From 7ba6462159025d958eb6703fe41d778bfb441800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yasin=20=C3=87al=C4=B1=C5=9Fkan?= Date: Tue, 15 Aug 2023 12:15:35 +0300 Subject: [PATCH 1/5] feat: Add asset txn forms --- src/core/home/sign-txn/create/CreateTxn.tsx | 284 ++++++++++++++++-- .../home/sign-txn/create/_create-txn.scss | 6 + .../create/button/CreateTxnButton.tsx | 69 ++++- src/core/transaction/transactionTypes.ts | 3 + 4 files changed, 334 insertions(+), 28 deletions(-) create mode 100644 src/core/transaction/transactionTypes.ts diff --git a/src/core/home/sign-txn/create/CreateTxn.tsx b/src/core/home/sign-txn/create/CreateTxn.tsx index 8ac1325..cd8f278 100644 --- a/src/core/home/sign-txn/create/CreateTxn.tsx +++ b/src/core/home/sign-txn/create/CreateTxn.tsx @@ -14,6 +14,8 @@ import { List, ListItem, Switch, + Tab, + TabItem, Textarea } from "@hipo/react-ui-toolkit"; import {useState} from "react"; @@ -24,6 +26,7 @@ import {ChainType, clientForChain} from "../../../utils/algod/algod"; import CreateTxnButton from "./button/CreateTxnButton"; import {separateIntoChunks} from "../../../utils/array/arrayUtils"; import {ALGORAND_DEFAULT_TXN_WAIT_ROUNDS, TRANSACTION_IN_GROUP_LIMIT} from "../../../transaction/transactionConstants"; +import {AssetTransactionType, PeraTransactionType} from "../../../transaction/transactionTypes"; interface CreateTxnModalProps { chain: ChainType; @@ -42,6 +45,8 @@ export interface TxnForm { rekeyTo: string; closeTo: string; transactionAmount: number; + + // keyreg voteKey?: string; selectionKey?: string; stateProofKey?: string; @@ -49,12 +54,50 @@ export interface TxnForm { voteLast?: number; voteKeyDilution?: number; isOnlineKeyregTxn?: boolean; + + // acfg + assetTxnType?: AssetTransactionType; + unitName?: string; + assetName?: string; + defaultFrozen?: boolean; + manager?: string; + reserve?: string; + freeze?: string; + clawback?: string; + assetURL?: string; + total?: number; + decimals?: number; } +const TXN_DROPDOWN_OPTIONS: DropdownOption[] = [ + { + id: "pay", + title: "pay" + }, + { + id: "axfer", + title: "axfer" + }, + { + id: "keyreg", + title: "keyreg" + }, + { + id: "acfg", + title: "acfg" + } +]; + +const ASSET_TXN_TABS: TabItem[] = [ + {id: "create", content: "Create"}, + {id: "modify", content: "Modify"}, + {id: "destroy", content: "Destroy"} +] + function CreateTxn({chain, address, isOpen, onClose, peraWallet}: CreateTxnModalProps) { const [transactions, setTransactions] = useState([]); const [transactionDropdownOption, setTransactionDropdownOption] = - useState | null>({ + useState | null>({ id: "pay", title: "pay" }); @@ -84,20 +127,7 @@ function CreateTxn({chain, address, isOpen, onClose, peraWallet}: CreateTxnModal { setTransactionDropdownOption(option); @@ -117,17 +147,6 @@ function CreateTxn({chain, address, isOpen, onClose, peraWallet}: CreateTxnModal {renderForm()} - - - setFormState({...formState, transactionAmount: Number(e.currentTarget.value)}) - } - /> - - @@ -281,6 +300,17 @@ function CreateTxn({chain, address, isOpen, onClose, peraWallet}: CreateTxnModal } /> + + + + setFormState({...formState, transactionAmount: Number(e.currentTarget.value)}) + } + /> + ); @@ -368,6 +398,15 @@ function CreateTxn({chain, address, isOpen, onClose, peraWallet}: CreateTxnModal )} + ); + + case "acfg": + return ( + <> + + {getAssetTransactionForms()} + + ) default: @@ -375,6 +414,199 @@ function CreateTxn({chain, address, isOpen, onClose, peraWallet}: CreateTxnModal } } + function handleAssetTabChange(index: number) { + let txnType: AssetTransactionType = "create"; + + if (index === 0) txnType = "create"; + else if (index === 1) txnType = "modify"; + else txnType = "destroy"; + + setFormState({...formState, assetTxnType: txnType}) + } + + function getAssetTransactionForms() { + const create = ( + <> + + + setFormState({...formState, unitName: e.currentTarget.value}) + } + /> + + + + + setFormState({...formState, assetName: e.currentTarget.value}) + } + /> + + + + + setFormState({...formState, manager: e.currentTarget.value}) + } + /> + + + + + + setFormState({...formState, reserve: e.currentTarget.value}) + } + /> + + + + + + setFormState({...formState, freeze: e.currentTarget.value}) + } + /> + + + + + + setFormState({...formState, clawback: e.currentTarget.value}) + } + /> + + + + + + setFormState({...formState, assetURL: e.currentTarget.value}) + } + /> + + + + + + setFormState({...formState, total: Number(e.currentTarget.value)}) + } + /> + + + + + + setFormState({...formState, decimals: Number(e.currentTarget.value)}) + } + /> + + + + setFormState({...formState, defaultFrozen: !formState.defaultFrozen})} /> + + + ); + + const modify = ( + <> + + + setFormState({...formState, assetIndex: e.currentTarget.value}) + } + /> + + + + + setFormState({...formState, manager: e.currentTarget.value}) + } + /> + + + + + + setFormState({...formState, reserve: e.currentTarget.value}) + } + /> + + + + + + setFormState({...formState, freeze: e.currentTarget.value}) + } + /> + + + + + + setFormState({...formState, clawback: e.currentTarget.value}) + } + /> + + + ); + + const destroy = ( + <> + + + setFormState({...formState, assetIndex: e.currentTarget.value}) + } + /> + + + ); + + return [create, modify, destroy]; + } + function handleSetTransactions(newTxns: SignerTransaction[]) { setTransactions([...transactions, ...newTxns]); } diff --git a/src/core/home/sign-txn/create/_create-txn.scss b/src/core/home/sign-txn/create/_create-txn.scss index 6a7659d..8021abd 100644 --- a/src/core/home/sign-txn/create/_create-txn.scss +++ b/src/core/home/sign-txn/create/_create-txn.scss @@ -17,3 +17,9 @@ border-radius: 4px; } + +.create-txn__asset-tab { + .tab__header { + margin-bottom: 30px; + } +} diff --git a/src/core/home/sign-txn/create/button/CreateTxnButton.tsx b/src/core/home/sign-txn/create/button/CreateTxnButton.tsx index ad10b04..f921000 100644 --- a/src/core/home/sign-txn/create/button/CreateTxnButton.tsx +++ b/src/core/home/sign-txn/create/button/CreateTxnButton.tsx @@ -8,6 +8,7 @@ import peraApi, {Asset} from "../../../../utils/pera/api/peraApi"; import {getSearchParams} from "../../../../utils/url/urlUtils"; import {TxnForm} from "../CreateTxn"; import {ChainType, apiGetTxnParams} from "../../../../utils/algod/algod"; +import {PeraTransactionType} from "../../../../transaction/transactionTypes"; function CreateTxnButton({ txnForm, @@ -17,7 +18,7 @@ function CreateTxnButton({ onSetTransactions }: { txnForm: TxnForm; - type: "pay" | "axfer" | "keyreg"; + type: PeraTransactionType; chain: ChainType; onResetForm: VoidFunction; onSetTransactions: (txns: SignerTransaction[]) => void; @@ -31,13 +32,29 @@ function CreateTxnButton({ rekeyTo, closeTo, transactionAmount, + + // keyreg voteKey, selectionKey, stateProofKey, voteFirst, voteLast, voteKeyDilution, - isOnlineKeyregTxn + isOnlineKeyregTxn, + + // acfg + + assetTxnType, + unitName, + assetName, + defaultFrozen, + manager, + reserve, + freeze, + clawback, + assetURL, + total, + decimals } = txnForm; const {runAsyncProcess} = useAsyncProcess>(); const assetsRef = useRef>(); @@ -77,6 +94,8 @@ function CreateTxnButton({ await createAxferTransaction(); } else if (type === "keyreg") { await createKeyregTransaction(); + } else if (type === "acfg") { + await createAcfgTransaction(); } onResetForm(); @@ -85,6 +104,52 @@ function CreateTxnButton({ } } + async function createAcfgTransaction() { + try { + const suggestedParams = await apiGetTxnParams(chain); + let txn: Transaction; + + if (assetTxnType === "create") { + txn = algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject({ + defaultFrozen: defaultFrozen || false, + unitName, + assetName, + manager, + reserve, + freeze, + clawback, + assetURL, + total: total || 1, + decimals: decimals || 0, + from: address, + suggestedParams + }); + } else if (assetTxnType === "modify") { + txn = algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject({ + from: address, + manager, + freeze, + clawback, + reserve, + assetIndex: Number(assetIndex), + suggestedParams, + strictEmptyAddressChecking: false + }) + } else { + txn = algosdk.makeAssetDestroyTxnWithSuggestedParamsFromObject({ + from: address, + suggestedParams, + assetIndex: Number(assetIndex) + }) + } + + + onSetTransactions([{txn}]); + } catch (e) { + console.log(e); + } + } + async function createKeyregTransaction() { try { const suggestedParams = await apiGetTxnParams(chain); diff --git a/src/core/transaction/transactionTypes.ts b/src/core/transaction/transactionTypes.ts new file mode 100644 index 0000000..574ba8a --- /dev/null +++ b/src/core/transaction/transactionTypes.ts @@ -0,0 +1,3 @@ +export type PeraTransactionType = "pay" | "axfer" | "keyreg" | "acfg"; + +export type AssetTransactionType = "create" | "modify" | "destroy"; \ No newline at end of file From 37a85a1386981f82ed7f2a43ed3ba6f740816069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yasin=20=C3=87al=C4=B1=C5=9Fkan?= Date: Thu, 17 Aug 2023 15:12:43 +0300 Subject: [PATCH 2/5] fix:(asset-txn): Set default asset txn type --- src/core/home/Home.tsx | 14 +++++++++----- src/core/home/sign-txn/create/CreateTxn.tsx | 3 ++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/core/home/Home.tsx b/src/core/home/Home.tsx index 7728311..a281a89 100644 --- a/src/core/home/Home.tsx +++ b/src/core/home/Home.tsx @@ -198,11 +198,15 @@ function Home() { } async function handleConnectWalletClick() { - const newAccounts = await peraWallet.connect(); - - handleSetLog("Connected to Pera Wallet"); - - setAccountAddress(newAccounts[0]); + try { + const newAccounts = await peraWallet.connect(); + + handleSetLog("Connected to Pera Wallet"); + + setAccountAddress(newAccounts[0]); + } catch (e) { + console.log(e) + } } function handleDisconnectWalletClick() { diff --git a/src/core/home/sign-txn/create/CreateTxn.tsx b/src/core/home/sign-txn/create/CreateTxn.tsx index cd8f278..959e8a5 100644 --- a/src/core/home/sign-txn/create/CreateTxn.tsx +++ b/src/core/home/sign-txn/create/CreateTxn.tsx @@ -109,7 +109,8 @@ function CreateTxn({chain, address, isOpen, onClose, peraWallet}: CreateTxnModal assetIndex: "", rekeyTo: "", closeTo: "", - transactionAmount: 1 + transactionAmount: 1, + assetTxnType: "create" }); const [sendBlockchain, setSendBlockchain] = useState(false); From d48e4989d39fea0fb7d2cec37d212e8c16bcb75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yasin=20=C3=87al=C4=B1=C5=9Fkan?= Date: Fri, 18 Aug 2023 12:17:28 +0300 Subject: [PATCH 3/5] build: Install `react-error-overlay` --- package.json | 22 ++++++++-------------- src/core/home/sign-txn/SignTxn.tsx | 1 + 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index bafa7e3..f8ca920 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "pera-demo-dapp", "version": "0.1.0", - "homepage": "https://perawallet.github.io/pera-demo-dapp/", + "homepage": ".", "private": true, "dependencies": { - "@hipo/react-ui-toolkit": "^1.0.0-beta", + "@hipo/react-ui-toolkit": "1.0.0-beta", "@perawallet/connect": "^1.3.1", "@perawallet/onramp": "^1.1.1", "@testing-library/jest-dom": "^5.16.2", @@ -31,6 +31,7 @@ "eslint-config-prettier": "^8.3.0", "eslint-import-resolver-typescript": "^2.5.0", "prettier": "^2.5.1", + "react-error-overlay": "^6.0.9", "sass": "1.56.1", "stylelint": "^13.12.0", "stylelint-no-unsupported-browser-features": "^4.1.4", @@ -52,18 +53,11 @@ "eslintIgnore": [ "signTxnUtils.tsx" ], - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, + "browserslist": [ + ">0.2%", + "not dead", + "not op_mini all" + ], "keywords": [], "description": "" } diff --git a/src/core/home/sign-txn/SignTxn.tsx b/src/core/home/sign-txn/SignTxn.tsx index d7102a3..359bd85 100644 --- a/src/core/home/sign-txn/SignTxn.tsx +++ b/src/core/home/sign-txn/SignTxn.tsx @@ -148,6 +148,7 @@ function SignTxn({ } } catch (error) { handleSetLog(`${error}`); + console.log(error); } finally { setIsRequestPending(false); refecthAccountDetail(); From ed64b13f9b8b4309e1525a2f22de06fe784b883b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yasin=20=C3=87al=C4=B1=C5=9Fkan?= Date: Mon, 25 Sep 2023 17:39:57 +0200 Subject: [PATCH 4/5] Add "Sign single app opt-in with rekey" txn --- src/core/home/sign-txn/util/signTxnUtils.tsx | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/core/home/sign-txn/util/signTxnUtils.tsx b/src/core/home/sign-txn/util/signTxnUtils.tsx index b2031b3..e8da9c7 100644 --- a/src/core/home/sign-txn/util/signTxnUtils.tsx +++ b/src/core/home/sign-txn/util/signTxnUtils.tsx @@ -350,6 +350,29 @@ const singleAppOptIn: Scenario = async ( }; }; +const singleAppOptInWithAppRekey: Scenario = async ( + chain: ChainType +): Promise => { + const suggestedParams = await apiGetTxnParams(chain); + + const appIndex = getAppIndex(chain); + + const txn = algosdk.makeApplicationOptInTxnFromObject({ + from: testAccounts[1].addr, + appIndex, + note: new Uint8Array(Buffer.from("example note value")), + appArgs: [Uint8Array.from([0]), Uint8Array.from([0, 1])], + rekeyTo: testAccounts[2].addr, + suggestedParams + }); + + const txnsToSign = [{txn}]; + + return { + transaction: [txnsToSign] + }; +}; + const singleAppCall: Scenario = async ( chain: ChainType, address: string @@ -2927,6 +2950,10 @@ export const scenarios: Array<{name: string; scenario: Scenario}> = [ { name: "60. 512 Transactions", scenario: fiveHundredTxns + }, + { + name: "61. Sign single app opt-in with rekey", + scenario: singleAppOptInWithAppRekey } ]; From bd508633ac923fe153d45a683bebb7a162683865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yasin=20=C3=87al=C4=B1=C5=9Fkan?= Date: Tue, 3 Oct 2023 11:06:44 +0200 Subject: [PATCH 5/5] fix: Provide the homepage --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8ca920..e3bfa54 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pera-demo-dapp", "version": "0.1.0", - "homepage": ".", + "homepage": "https://perawallet.github.io/pera-demo-dapp/", "private": true, "dependencies": { "@hipo/react-ui-toolkit": "1.0.0-beta",