From 5f273b21d889153cd378a80d5e845b7bbab3a2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jeesun=20=EC=A7=80=EC=84=A0?= Date: Mon, 15 Apr 2024 10:46:10 -0700 Subject: [PATCH 1/3] [Sign TX] Input Overview View (#826) --- package.json | 2 +- src/app/(sidebar)/transaction/sign/page.tsx | 198 ++++++++++++++++++- src/components/FormElements/PubKeyPicker.tsx | 2 + src/components/FormElements/TextPicker.tsx | 33 ++-- src/components/FormElements/XdrPicker.tsx | 42 ++-- src/components/MuxedAccountResult.tsx | 6 +- src/constants/signTransactionPage.ts | 62 ++++++ src/helpers/trim.ts | 3 + src/styles/globals.scss | 61 +++++- src/validate/methods/xdr.ts | 36 +++- yarn.lock | 28 +-- 11 files changed, 406 insertions(+), 67 deletions(-) create mode 100644 src/constants/signTransactionPage.ts create mode 100644 src/helpers/trim.ts diff --git a/package.json b/package.json index 95a6b0bf..a542729d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "git-info": "rm -rf src/generated/ && mkdir src/generated/ && echo export default \"{\\\"commitHash\\\": \\\"$(git rev-parse --short HEAD)\\\", \\\"version\\\": \\\"$(git describe --tags --always)\\\"};\" > src/generated/gitInfo.ts" }, "dependencies": { - "@stellar/design-system": "^2.0.0-beta.9", + "@stellar/design-system": "^2.0.0-beta.10", "@stellar/stellar-sdk": "^11.3.0", "@tanstack/react-query": "^5.28.8", "@tanstack/react-query-devtools": "^5.28.8", diff --git a/src/app/(sidebar)/transaction/sign/page.tsx b/src/app/(sidebar)/transaction/sign/page.tsx index 326001a7..ad69a014 100644 --- a/src/app/(sidebar)/transaction/sign/page.tsx +++ b/src/app/(sidebar)/transaction/sign/page.tsx @@ -1,5 +1,201 @@ "use client"; +import { useState } from "react"; +import { Alert, Card, Icon, Text, Button } from "@stellar/design-system"; +import { + TransactionBuilder, + FeeBumpTransaction, + Transaction, +} from "@stellar/stellar-sdk"; + +import { useStore } from "@/store/useStore"; + +import { XdrPicker } from "@/components/FormElements/XdrPicker"; +import { TextPicker } from "@/components/FormElements/TextPicker"; +import { validate } from "@/validate"; + +import { FEE_BUMP_TX_FIELDS, TX_FIELDS } from "@/constants/signTransactionPage"; + +const MIN_LENGTH_FOR_FULL_WIDTH_FIELD = 30; + export default function SignTransaction() { - return
Sign Transaction
; + const { network } = useStore(); + const [txEnv, setTxEnv] = useState(""); + const [isTxValid, setIsTxValid] = useState(undefined); + const [txErrMsg, setTxErrMsg] = useState(""); + const [txSuccessMsg, setTxSuccessMsg] = useState(""); + const [tx, setTx] = useState( + undefined, + ); + const [isTxImported, setIsTxImported] = useState(false); + + const onChange = (value: string) => { + setTxErrMsg(""); + setTxSuccessMsg(""); + setTxEnv(value); + + if (value.length > 0) { + const validatedXDR = validate.xdr(value); + + if (validatedXDR.result === "success") { + setIsTxValid(true); + setTxSuccessMsg(validatedXDR.message); + } else { + setIsTxValid(false); + setTxErrMsg(validatedXDR.message); + } + } + }; + + 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 = () => { + return ( + <> + +
+ + Import a transaction envelope in XDR format + + + + + } + value={txEnv || ""} + error={txErrMsg} + success={ + + {txSuccessMsg} + + } + onChange={(e) => onChange(e.target.value)} + /> + +
+ +
+
+
+ + + + The transaction signer lets you add signatures to a Stellar + transaction. Signatures are used in the network to prove that the + account is authorized to perform the operations in the transaction. + + + For simple transactions, you only need one signature from the + correct account. Some advanced transactions may require more than + one signature if there are multiple source accounts or signing keys. + + + + ); + }; + + const renderOverviewView = () => { + if (!tx) { + return null; + } + + const REQUIRED_FIELDS = [ + { + label: "Signing for", + value: network.passphrase, + }, + { + label: "Transaction Envelope XDR", + value: txEnv, + }, + { + label: "Transaction Hash", + value: tx.hash().toString("hex"), + }, + ]; + + let mergedFields; + + if (tx instanceof FeeBumpTransaction) { + mergedFields = [...REQUIRED_FIELDS, ...FEE_BUMP_TX_FIELDS(tx)]; + } else { + mergedFields = [...REQUIRED_FIELDS, ...TX_FIELDS(tx)]; + } + + return ( + <> + +
+ {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 ( +
+ +
+ ); + } else { + return ( +
+ +
+ ); + } + })} +
+
+ + ); + }; + + return ( +
+
+ + {isTxImported ? "Transaction Overview" : "Sign Transaction"} + +
+ {isTxValid && tx ? renderOverviewView() : rendeImportView()} +
+ ); } diff --git a/src/components/FormElements/PubKeyPicker.tsx b/src/components/FormElements/PubKeyPicker.tsx index becca362..e76c2133 100644 --- a/src/components/FormElements/PubKeyPicker.tsx +++ b/src/components/FormElements/PubKeyPicker.tsx @@ -21,6 +21,7 @@ export const PubKeyPicker = ({ placeholder = "Ex: GCEXAMPLE5HWNK4AYSTEQ4UWDKHTCKADVS2AHF3UI2ZMO3DPUSM6Q4UG", value, error, + readOnly, onChange, ...props }: PubKeyPickerProps) => ( @@ -32,6 +33,7 @@ export const PubKeyPicker = ({ placeholder={placeholder} value={value} error={error} + readOnly={readOnly} onChange={onChange} {...props} /> diff --git a/src/components/FormElements/TextPicker.tsx b/src/components/FormElements/TextPicker.tsx index 2adf54f0..7e90cba9 100644 --- a/src/components/FormElements/TextPicker.tsx +++ b/src/components/FormElements/TextPicker.tsx @@ -8,8 +8,9 @@ interface TextPickerProps extends Omit { label: string; value: string; placeholder?: string; - error: string | undefined; - onChange: (e: React.ChangeEvent) => void; + error?: string | undefined; + readOnly?: boolean; + onChange?: (e: React.ChangeEvent) => void; } export const TextPicker = ({ @@ -20,18 +21,18 @@ export const TextPicker = ({ value, error, onChange, + readOnly, ...props -}: TextPickerProps) => { - return ( - - ); -}; +}: TextPickerProps) => ( + +); diff --git a/src/components/FormElements/XdrPicker.tsx b/src/components/FormElements/XdrPicker.tsx index a75ee44d..54196830 100644 --- a/src/components/FormElements/XdrPicker.tsx +++ b/src/components/FormElements/XdrPicker.tsx @@ -5,11 +5,13 @@ interface XdrPickerProps extends Omit { id: string; fieldSize?: "sm" | "md" | "lg"; labelSuffix?: string | React.ReactNode; - label: string; + label: string | React.ReactNode; value: string; placeholder?: string; - error: string | undefined; - onChange: (e: React.ChangeEvent) => void; + error?: string | undefined; + success?: string | React.ReactNode; + onChange?: (e: React.ChangeEvent) => void; + readOnly?: boolean; } export const XdrPicker = ({ @@ -19,21 +21,23 @@ export const XdrPicker = ({ label, value, error, + success, onChange, + readOnly, ...props -}: XdrPickerProps) => { - return ( -