From 4a234dff9bc15a6a5cefa9d7fa1b3a5018d9e0e2 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Wed, 21 Aug 2024 18:12:04 -0400 Subject: [PATCH 1/3] chore(demo): add basic avm-provider --- package-lock.json | 166 ++++++++++- sites/dapp-ui/package.json | 4 +- sites/dapp-ui/src/App.tsx | 2 +- sites/dapp-ui/src/components/ArcMessage.tsx | 24 ++ sites/dapp-ui/src/components/MessageCard.tsx | 46 ---- .../src/components/TransactionCard.tsx | 40 --- sites/dapp-ui/src/entry-main.tsx | 1 - sites/dapp-ui/src/hooks/useDataChannel.ts | 7 +- sites/dapp-ui/src/hooks/useSignalClient.ts | 2 + sites/dapp-ui/src/lib/provider.ts | 21 ++ sites/dapp-ui/src/pages/connected.tsx | 260 +++++++++++------- sites/dapp-ui/src/store.ts | 105 ++----- sites/dapp-ui/vite.config.ts | 9 +- 13 files changed, 400 insertions(+), 287 deletions(-) create mode 100644 sites/dapp-ui/src/components/ArcMessage.tsx delete mode 100644 sites/dapp-ui/src/components/MessageCard.tsx delete mode 100644 sites/dapp-ui/src/components/TransactionCard.tsx create mode 100644 sites/dapp-ui/src/lib/provider.ts diff --git a/package-lock.json b/package-lock.json index 9e7a735..00bd86d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,48 @@ "node": ">=0.10.0" } }, + "node_modules/@algorandfoundation/algo-models": { + "version": "0.0.3", + "resolved": "git+ssh://git@github.com/awesome-algorand/algo-models.git#2d79493c7008a0a0de7eef7d8a2584a8f5791672", + "license": "AGPL-3.0-or-later", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "algo-msgpack-with-bigint": "^2.1.1", + "cbor-x": "^1.6.0", + "hi-base32": "^0.5.1", + "js-sha512": "^0.8.0", + "uuid": "^10.0.0" + } + }, + "node_modules/@algorandfoundation/algo-models/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@algorandfoundation/algo-models/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@algorandfoundation/liquid-client": { "version": "0.0.1", "resolved": "git+ssh://git@github.com/algorandfoundation/liquid-auth-js.git#334a3434afaf3dc4fab62bcb14ee152922501c57", @@ -2075,6 +2117,78 @@ "dev": true, "license": "MIT" }, + "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz", + "integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-darwin-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz", + "integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz", + "integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-arm64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz", + "integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-linux-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz", + "integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@cbor-extract/cbor-extract-win32-x64": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz", + "integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@colors/colors": { "version": "1.5.0", "dev": true, @@ -6618,7 +6732,6 @@ }, "node_modules/ajv": { "version": "8.12.0", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -7745,6 +7858,35 @@ "node": ">=6.0.0" } }, + "node_modules/cbor-extract": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz", + "integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.1.1" + }, + "bin": { + "download-cbor-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@cbor-extract/cbor-extract-darwin-arm64": "2.2.0", + "@cbor-extract/cbor-extract-darwin-x64": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm": "2.2.0", + "@cbor-extract/cbor-extract-linux-arm64": "2.2.0", + "@cbor-extract/cbor-extract-linux-x64": "2.2.0", + "@cbor-extract/cbor-extract-win32-x64": "2.2.0" + } + }, + "node_modules/cbor-x": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.6.0.tgz", + "integrity": "sha512-0kareyRwHSkL6ws5VXHEf8uY1liitysCVJjlmhaLG+IXLqhSaOO+t63coaso7yjwEzWZzLy8fJo06gZDVQM9Qg==", + "optionalDependencies": { + "cbor-extract": "^2.2.0" + } + }, "node_modules/chalk": { "version": "4.1.2", "license": "MIT", @@ -9648,7 +9790,6 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -11871,7 +12012,6 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -12732,6 +12872,20 @@ "node": "^12.13 || ^14.13 || >=16" } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/rimraf": { "version": "3.0.2", "license": "ISC", @@ -13888,7 +14042,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -15993,7 +16146,6 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -17165,7 +17317,8 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "@algorandfoundation/liquid-client": "github:algorandfoundation/liquid-auth-js#chore/switch-request-id-to-uuid", + "@algorandfoundation/algo-models": "github:awesome-algorand/algo-models", + "@algorandfoundation/liquid-client": "github:algorandfoundation/liquid-auth-js", "@emotion/react": "^11.11.3", "@emotion/server": "^11.11.0", "@emotion/styled": "^11.11.0", @@ -17173,6 +17326,7 @@ "@mui/material": "^5.15.4", "@tanstack/react-query": "^5.20.5", "@tanstack/react-query-devtools": "^5.20.5", + "cbor-x": "^1.6.0", "qr-code-styling": "^1.6.0-rc.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/sites/dapp-ui/package.json b/sites/dapp-ui/package.json index 18c0516..1414201 100644 --- a/sites/dapp-ui/package.json +++ b/sites/dapp-ui/package.json @@ -12,7 +12,8 @@ "lint": "eslint \"{src,apps,libs,test}/**/*.{ts,tsx}\" --fix" }, "dependencies": { - "@algorandfoundation/liquid-client": "github:algorandfoundation/liquid-auth-js#chore/switch-request-id-to-uuid", + "@algorandfoundation/algo-models": "github:awesome-algorand/algo-models", + "@algorandfoundation/liquid-client": "github:algorandfoundation/liquid-auth-js", "@emotion/react": "^11.11.3", "@emotion/server": "^11.11.0", "@emotion/styled": "^11.11.0", @@ -20,6 +21,7 @@ "@mui/material": "^5.15.4", "@tanstack/react-query": "^5.20.5", "@tanstack/react-query-devtools": "^5.20.5", + "cbor-x": "^1.6.0", "qr-code-styling": "^1.6.0-rc.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/sites/dapp-ui/src/App.tsx b/sites/dapp-ui/src/App.tsx index 62639b6..2572213 100644 --- a/sites/dapp-ui/src/App.tsx +++ b/sites/dapp-ui/src/App.tsx @@ -14,7 +14,7 @@ import { Algodv2 } from 'algosdk'; import { AlgodContext } from '@/hooks'; import { SignalClientContext } from '@/hooks/useSignalClient.ts'; import { LinkMessage, SignalClient } from '@algorandfoundation/liquid-client'; -import { useAddressStore } from '@/store'; +import { useAddressStore } from '@/store.ts'; const queryClient = new QueryClient(); const algod = new Algodv2( diff --git a/sites/dapp-ui/src/components/ArcMessage.tsx b/sites/dapp-ui/src/components/ArcMessage.tsx new file mode 100644 index 0000000..f32f03f --- /dev/null +++ b/sites/dapp-ui/src/components/ArcMessage.tsx @@ -0,0 +1,24 @@ +import { MessageState } from "@/store.ts"; +import Card from "@mui/material/Card"; +import { CardHeader } from "@mui/material"; +import Typography from "@mui/material/Typography"; +import CardContent from "@mui/material/CardContent"; + +export function ArcMessage(state: MessageState) { + return ( + + + + + {state.status} + + + {JSON.stringify(state.message)} + + + + ); +} diff --git a/sites/dapp-ui/src/components/MessageCard.tsx b/sites/dapp-ui/src/components/MessageCard.tsx deleted file mode 100644 index 34d5e70..0000000 --- a/sites/dapp-ui/src/components/MessageCard.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import Typography from '@mui/material/Typography'; -import { Message } from '@/store.ts'; -import { CardHeader } from '@mui/material'; - -export function MessageCard({ msg }: { msg: Message }) { - let message; - const isLocal = msg.type === 'local'; - switch (msg.data.type) { - case 'credential': - message = isLocal - ? `🔑 Credential Sent: ${msg.data.id}` - : `🔑 Credential Received: ${msg.data.id}`; - break; - case 'transaction': - message = isLocal - ? `🚚 Transaction Sent: ${msg.data.txId}` - : `🚚 Transaction Received: ${msg.data.txId}`; - break; - case 'transaction-signature': - message = isLocal - ? `🔏 Signature Sent: ${msg.data.txId}` - : `🔏 Signature Received: ${msg.data.txId}`; - break; - default: - message = 'Unknown message'; - } - return ( - - - - Message - - - - - {message} - - - - ); -} diff --git a/sites/dapp-ui/src/components/TransactionCard.tsx b/sites/dapp-ui/src/components/TransactionCard.tsx deleted file mode 100644 index a1b01f5..0000000 --- a/sites/dapp-ui/src/components/TransactionCard.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import Card from '@mui/material/Card'; -import { LiquidTransaction } from '@/store.ts'; -import { CardHeader, Link } from '@mui/material'; - -export function TransactionCard({ txn }: { txn: LiquidTransaction }) { - const isLive = txn.status === 'confirmed'; - let emoji; - switch (txn.status) { - case 'confirmed': - emoji = '✅'; - break; - case 'submitted': - emoji = '🔵'; - break; - case 'failed': - emoji = '❌'; - break; - default: - emoji = '🟡'; - } - return ( - - - {txn.txn.txID()} - - ) : ( - txn.txn.txID() - ) - } - > - - ); -} diff --git a/sites/dapp-ui/src/entry-main.tsx b/sites/dapp-ui/src/entry-main.tsx index f6db3f5..f3133a6 100644 --- a/sites/dapp-ui/src/entry-main.tsx +++ b/sites/dapp-ui/src/entry-main.tsx @@ -2,7 +2,6 @@ import './index.css'; import * as ReactDOM from 'react-dom/client'; // import { StrictMode } from 'react'; import App from './App.tsx'; - ReactDOM.createRoot(document.getElementById('root')!).render( // , diff --git a/sites/dapp-ui/src/hooks/useDataChannel.ts b/sites/dapp-ui/src/hooks/useDataChannel.ts index 3d37cd4..10082e9 100644 --- a/sites/dapp-ui/src/hooks/useDataChannel.ts +++ b/sites/dapp-ui/src/hooks/useDataChannel.ts @@ -7,12 +7,17 @@ import { SignalClientContext } from '@/hooks/useSignalClient.ts'; */ export function useDataChannel(onMessage?: (event: MessageEvent) => void) { const { dataChannel } = useContext(SignalClientContext); + function onError(...args: any[]) { + console.error('Data channel error', args); + } useEffect(() => { if (!dataChannel || !onMessage) return; dataChannel.addEventListener('message', onMessage); + dataChannel.addEventListener('error', onError) return () => { dataChannel.removeEventListener('message', onMessage); - }; + dataChannel.removeEventListener('error', onError); + } }, [dataChannel, onMessage]); return dataChannel; diff --git a/sites/dapp-ui/src/hooks/useSignalClient.ts b/sites/dapp-ui/src/hooks/useSignalClient.ts index f75f163..e5322b4 100644 --- a/sites/dapp-ui/src/hooks/useSignalClient.ts +++ b/sites/dapp-ui/src/hooks/useSignalClient.ts @@ -8,6 +8,8 @@ type SignalClientState = { setStatus: (_: 'connected' | 'disconnected') => void; dataChannel: RTCDataChannel | null; setDataChannel: (_: RTCDataChannel | null) => void; + loading: boolean; + setLoading: (_: boolean) => void; }; export const SignalClientContext = createContext({ client: null, diff --git a/sites/dapp-ui/src/lib/provider.ts b/sites/dapp-ui/src/lib/provider.ts new file mode 100644 index 0000000..fcc9d5e --- /dev/null +++ b/sites/dapp-ui/src/lib/provider.ts @@ -0,0 +1,21 @@ +import {decode} from 'cbor-x' +import { decodeUnsignedTransaction } from "algosdk"; +import { IARC0001Transaction, ResponseMessage, Results, fromBase64Url } from "@algorandfoundation/algo-models/provider"; + +export function attachEncodedSignature(address: string, txn: string, signature: string){ + return decodeUnsignedTransaction(fromBase64Url(txn)) + .attachSignature(address, fromBase64Url(signature)); +} + +export function attachSignedTransactionsFromResult(address: string, result: Results, txns: IARC0001Transaction[]){ + let stxns = []; + for(let i = 0; i < txns.length; i++){ + stxns.push(attachEncodedSignature(address, txns[i].txn, result.stxns[i]!!)) + } + return stxns +} + +export function fromResult(result: string): ResponseMessage { + return decode(fromBase64Url(result)) as ResponseMessage +} + diff --git a/sites/dapp-ui/src/pages/connected.tsx b/sites/dapp-ui/src/pages/connected.tsx index c9d41f2..483b69b 100644 --- a/sites/dapp-ui/src/pages/connected.tsx +++ b/sites/dapp-ui/src/pages/connected.tsx @@ -1,81 +1,136 @@ -import { useDataChannel } from '@/hooks/useDataChannel.ts'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from "react"; +import Box from '@mui/material/Box' +import Input from '@mui/material/Input' +import LinearProgress from '@mui/material/LinearProgress' +import Paper from '@mui/material/Paper' +import Slider from '@mui/material/Slider' import Button from '@mui/material/Button'; +import FormControl from '@mui/material/FormControl'; +import Typography from '@mui/material/Typography'; +import { useNavigate } from 'react-router-dom'; + import { + // TODO: Migrate to algo-models encodeUnsignedTransaction, + // TODO: Migrate to algo-fetch waitForConfirmation, + // TODO: Migrate to algo-models makePaymentTxnWithSuggestedParamsFromObject, } from 'algosdk'; -import { toBase64URL, fromBase64Url } from '@algorandfoundation/liquid-client'; + +import { RequestMessage, toSignTransactionsParamsRequestMessage, toBase64URL } from "@algorandfoundation/algo-models/provider"; +import { SignalClient } from "@algorandfoundation/liquid-client"; +import { + attachSignedTransactionsFromResult, + fromResult, +} from "@/lib/provider.js"; + +import { EmptyAccountCard } from '@/components/EmptyAccountCard.tsx'; +import { ArcMessage } from "@/components/ArcMessage.tsx"; + +import { useDataChannel } from '@/hooks/useDataChannel.ts'; import { useAlgod } from '@/hooks/useAlgod.ts'; import { useAccountInfo } from '@/hooks/useAccountInfo.ts'; -import FormControl from '@mui/material/FormControl'; -import { Box, Input, Paper, Slider } from '@mui/material'; -import Typography from '@mui/material/Typography'; + import { - useAddressStore, - useMessageStore, - useTransactionStore, -} from '../store.ts'; -import { useNavigate } from 'react-router-dom'; -import { MessageCard } from '@/components/MessageCard.tsx'; -import { TransactionCard } from '@/components/TransactionCard.tsx'; -import { EmptyAccountCard } from '@/components/EmptyAccountCard.tsx'; + useAddressStore, useARC27MessageStore, +} from "@/store.ts"; + export function ConnectedPage() { const navigate = useNavigate(); + + // Algorand Specific State const algod = useAlgod(); + // TODO: migrate to Use-Wallet-Liquid const wallet = useAddressStore((state) => state.address); + const [suggestedParams, setSuggestedParams] = useState(null) + // Account const accountInfo = useAccountInfo(wallet, 3000); + + // Payment Transaction Form const [from, setFrom] = useState(wallet); const [to, setTo] = useState(wallet); const [amount, setAmount] = useState(0); + const [count, setCount] = useState(1); + + // Sending indicator + const [progress, setProgress] = useState(0); + const [buffer, setBuffer] = useState(0); + + // Message Store + const addArcMessage = useARC27MessageStore((state) => state.addMessage); + const arcMessages = useARC27MessageStore((state) => state.messages); + const updateArcMessage = useARC27MessageStore((state) => state.updateMessage); - const addMessage = useMessageStore((state) => state.addMessage); - const messages = useMessageStore((state) => state.messages); - const addTransaction = useTransactionStore((state) => state.addTransaction); - const updateTransaction = useTransactionStore( - (state) => state.updateTransaction, - ); - const transactions = useTransactionStore((state) => state.transactions); // Receive response const datachannel = useDataChannel((event) => { - const message = JSON.parse(event.data); - // TODO: Handle P2P using a previously known credential - //if (message.type === 'credential') localStorage['credId'] = message.id; - if (message.type !== 'transaction-signature') return; - addMessage({ - type: 'remote', - data: JSON.parse(event.data), - timestamp: Date.now(), - }); async function handleMessage() { - const message = JSON.parse(event.data); - if (message.type === 'credential') localStorage['credId'] = message.id; - if (message.type !== 'transaction-signature') return; - const txn = transactions.find((txn) => txn.txId === message.txId); - if (!txn) return; - const sig = fromBase64Url(message.sig); - const signedTxn = txn.txn.attachSignature( - accountInfo.data?.['auth-addr'] - ? accountInfo.data?.['auth-addr'] - : wallet, - sig, - ); - updateTransaction(txn.txId, 'signed'); - const { txId } = await algod.sendRawTransaction(signedTxn).do(); - updateTransaction(txn.txId, 'submitted'); - await waitForConfirmation(algod, txId, 4); - updateTransaction(txn.txId, 'confirmed'); + try{ + // Ignore JSON messages + JSON.parse(event.data); + } catch (e) { + + // Fetch ResultMessage from the encoded string + let data = fromResult(event.data); + // Add the incoming message + addArcMessage({status: 'received', message: data}) + // Get matching request + const matchingRequests = arcMessages.filter((msg) => msg.message.id === data.requestId) + if(matchingRequests.length !== 1) throw new Error('Invalid request') + const request = matchingRequests[0] + updateArcMessage(request.message.id, 'received') + + // Get the transaction strings + const msgTxns = (request.message as RequestMessage).params.txns + + // Attach the signatures + let stxns = attachSignedTransactionsFromResult(wallet, data.result, msgTxns) + updateArcMessage(request.message.id, 'signed'); + + const confirmationPromises = await batchSignTransactions(stxns) + updateArcMessage(request.message.id, 'submitted'); + await Promise.all(confirmationPromises) + setProgress(0) + setBuffer(0) + updateArcMessage(request.message.id, 'confirmed'); + } + + } + async function batchSignTransactions(stxns: Uint8Array[]) { + let confirmationPromises = []; + for (let i = 0; i < stxns.length; i++) { + const { txId } = await algod.sendRawTransaction(stxns[i]).do(); + setBuffer((i+1)/stxns.length * 100) + confirmationPromises.push(waitForConfirmation(algod, txId, 4).then(()=>{ + setProgress((i+1)/stxns.length * 100) + })); + } + return confirmationPromises } handleMessage(); }); + // Redirect when there is no datachannel or wallet useEffect(() => { if (!datachannel || wallet === '') navigate('/'); }, [datachannel, wallet, navigate]); + // Load Suggested Params + useEffect(()=>{ + algod.getTransactionParams().do().then((params)=>{ + setSuggestedParams(params) + }); + }, []) + + // Max Account Spend + const maxSpend = useMemo(()=>{ + if(typeof accountInfo.data === 'undefined' || !suggestedParams) return 0; + const txnCost = (amount + suggestedParams.minFee) * count; + return Math.round(accountInfo.data.amount / txnCost) + }, [count, accountInfo.data, suggestedParams]) + if (accountInfo.data && accountInfo.data.amount === 0) { return ; } @@ -107,13 +162,27 @@ export function ConnectedPage() { setAmount(value as number)} /> + + + Amount of Transactions + + setCount(value as number)} + /> + + - Transactions + ARC27 Messages - - {transactions.map((transaction, i) => ( - - ))} - - - Messages - - - {messages.map((message, i) => ( - - ))} - + + + {arcMessages.map((message, i) => ( + + ))} + ); } diff --git a/sites/dapp-ui/src/store.ts b/sites/dapp-ui/src/store.ts index ffd89a2..c70d9dc 100644 --- a/sites/dapp-ui/src/store.ts +++ b/sites/dapp-ui/src/store.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; -import { Transaction } from 'algosdk'; +import { RequestMessage, ResponseMessage } from "@algorandfoundation/algo-models/provider"; interface AddressState { address: string; @@ -22,88 +22,29 @@ export const useAddressStore = create< ), ); -export type TransactionSignaturePayload = { - type: 'transaction-signature'; - txId: string; - sig: string; -}; -export type TransactionPayload = { - type: 'transaction'; - txId: string; - txn: string; -}; -// { -// "address": "PNNMCGV3XLEDFEGR7IHSGY5HHGAQQ4OC7H75SPGQUFVTNWV45JVXPRJULY", -// "device": "Pixel 8 Pro", -// "origin": "https://catfish-pro-wolf.ngrok-free.app", -// "id": "AdoMGSqp-ni0udT2e5RafkSJo2Czs0s-Ekr5wB06PIpXIhlG-qfdCyN_riM_enKwZnwQXwrFp3e9IB0VNLg6swM", -// "prevCounter": 2, -// "type": "credential" -// } -export type CredentialPayload = { - type: 'credential'; - id: string; - address: string; - device?: string; - origin: string; - prevCounter: number; -}; - -export type MessagePayload = { - type: 'transaction' | 'credential' | 'transaction-signature'; - // data: TransactionSignature | ; -}; -export type Message = { - data: - | TransactionPayload - | TransactionSignaturePayload - | CredentialPayload - | string; - type: 'local' | 'remote'; - timestamp: number; -}; - -interface MessageStore { - messages: Message[]; - addMessage: (message: Message) => void; - clearMessages: () => void; +export type MessageState = { + status: 'created' | 'sent' | 'received' | 'signed' | 'submitted' | 'confirmed' | 'failed'; + message: RequestMessage | ResponseMessage } -export const useMessageStore = create((set) => ({ - messages: [], - addMessage: (message: Message) => - set((state) => ({ messages: [...state.messages, message] })), - clearMessages: () => set({ messages: [] }), -})); - -export type LiquidTransaction = { - txn: Transaction; - txId: string; - sig?: string; - status: 'created' | 'sent' | 'signed' | 'submitted' | 'confirmed' | 'failed'; -}; - -interface TransactionStore { - transactions: LiquidTransaction[]; - addTransaction: (txn: LiquidTransaction) => void; - updateTransaction: ( - txId: string, - status: LiquidTransaction['status'], - ) => void; - clearTransactions: () => void; +export interface ARC27MessageStore { + messages: MessageState[]; + addMessage: (state: MessageState) => void; + updateMessage: (id: string, status: MessageState['status']) => void; + clearMessages: () => void; } -export const useTransactionStore = create((set) => ({ - transactions: [], - addTransaction: (txn: LiquidTransaction) => - set((state) => ({ transactions: [...state.transactions, txn] })), - updateTransaction: (txId: string, status: LiquidTransaction['status']) => - set((state) => ({ - transactions: state.transactions.map((txn) => { - if (txn.txId === txId) { - return { ...txn, status }; - } - return txn; - }), - })), - clearTransactions: () => set({ transactions: [] }), +export const useARC27MessageStore = create((set) => ({ + messages: [], + addMessage: (msg: MessageState) => set((state) => ({ messages: [...state.messages, msg] })), + updateMessage: (id: string, status: MessageState['status']) => + set((state) => ({ + messages: state.messages.map((msg) => { + if (msg.message.id === id) { + return { ...msg, status }; + } + return msg; + }), + }), + ), + clearMessages: () => set({ messages: [] }), })); diff --git a/sites/dapp-ui/vite.config.ts b/sites/dapp-ui/vite.config.ts index 2070be9..c4842b9 100644 --- a/sites/dapp-ui/vite.config.ts +++ b/sites/dapp-ui/vite.config.ts @@ -5,8 +5,8 @@ import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'; import react from '@vitejs/plugin-react-swc'; import { resolve } from 'node:path'; -const DEFAULT_PROXY_URL = 'http://localhost:3000'; -const DEFAULT_WSS_PROXY_URL = 'ws://localhost:3000'; +const DEFAULT_PROXY_URL = 'http://192.168.1.17:5173'; +const DEFAULT_WSS_PROXY_URL = 'ws://192.168.1.17:5173'; export default defineConfig({ server: { @@ -27,7 +27,8 @@ export default defineConfig({ '@/components': resolve(__dirname, 'src', 'components'), '@/hooks': resolve(__dirname, 'src', 'hooks'), '@/pages': resolve(__dirname, 'src', 'pages'), - '@/store': resolve(__dirname, 'src', 'store'), + '@/store.ts': resolve(__dirname, 'src', 'store.ts'), + '@/lib': resolve(__dirname, 'src', 'lib'), }, }, plugins: [ @@ -85,7 +86,7 @@ export default defineConfig({ ], }, }), - splitVendorChunkPlugin(), + // splitVendorChunkPlugin(), ViteImageOptimizer(), react(), ], From b4084e5df2ed7c8ae84e4b3caac38a39a66e2af7 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 26 Aug 2024 17:08:27 -0400 Subject: [PATCH 2/3] chore(demo): warn on explicit any --- sites/dapp-ui/.eslintrc.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/sites/dapp-ui/.eslintrc.cjs b/sites/dapp-ui/.eslintrc.cjs index 137bb37..74e31c0 100644 --- a/sites/dapp-ui/.eslintrc.cjs +++ b/sites/dapp-ui/.eslintrc.cjs @@ -11,6 +11,7 @@ module.exports = { parser: '@typescript-eslint/parser', plugins: ['react-refresh'], rules: { + '@typescript-eslint/no-explicit-any': 'warn', 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, From 307b48ca27df3013bfb31d25aba8a968ebd8043f Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 27 Aug 2024 12:11:18 -0400 Subject: [PATCH 3/3] chore(demo): update to new package for provider --- package-lock.json | 56 +++++++-------------------- sites/dapp-ui/package.json | 2 +- sites/dapp-ui/src/lib/provider.ts | 2 +- sites/dapp-ui/src/pages/connected.tsx | 2 +- 4 files changed, 16 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00bd86d..c4a48b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,48 +73,6 @@ "node": ">=0.10.0" } }, - "node_modules/@algorandfoundation/algo-models": { - "version": "0.0.3", - "resolved": "git+ssh://git@github.com/awesome-algorand/algo-models.git#2d79493c7008a0a0de7eef7d8a2584a8f5791672", - "license": "AGPL-3.0-or-later", - "dependencies": { - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "algo-msgpack-with-bigint": "^2.1.1", - "cbor-x": "^1.6.0", - "hi-base32": "^0.5.1", - "js-sha512": "^0.8.0", - "uuid": "^10.0.0" - } - }, - "node_modules/@algorandfoundation/algo-models/node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@algorandfoundation/algo-models/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@algorandfoundation/liquid-client": { "version": "0.0.1", "resolved": "git+ssh://git@github.com/algorandfoundation/liquid-auth-js.git#334a3434afaf3dc4fab62bcb14ee152922501c57", @@ -142,6 +100,13 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@algorandfoundation/provider": { + "version": "0.0.1", + "resolved": "git+ssh://git@github.com/algorandfoundation/wallet-provider-ts.git#28c80f5b9e0259b8e83e65c65d802d8123de9046", + "dependencies": { + "cbor-x": "^1.6.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "dev": true, @@ -6732,6 +6697,7 @@ }, "node_modules/ajv": { "version": "8.12.0", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -9790,6 +9756,7 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -12012,6 +11979,7 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -14042,6 +14010,7 @@ }, "node_modules/require-from-string": { "version": "2.0.2", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -16146,6 +16115,7 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -17317,8 +17287,8 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "@algorandfoundation/algo-models": "github:awesome-algorand/algo-models", "@algorandfoundation/liquid-client": "github:algorandfoundation/liquid-auth-js", + "@algorandfoundation/provider": "github:algorandfoundation/wallet-provider-ts", "@emotion/react": "^11.11.3", "@emotion/server": "^11.11.0", "@emotion/styled": "^11.11.0", diff --git a/sites/dapp-ui/package.json b/sites/dapp-ui/package.json index 1414201..7ce307b 100644 --- a/sites/dapp-ui/package.json +++ b/sites/dapp-ui/package.json @@ -12,7 +12,7 @@ "lint": "eslint \"{src,apps,libs,test}/**/*.{ts,tsx}\" --fix" }, "dependencies": { - "@algorandfoundation/algo-models": "github:awesome-algorand/algo-models", + "@algorandfoundation/provider": "github:algorandfoundation/wallet-provider-ts", "@algorandfoundation/liquid-client": "github:algorandfoundation/liquid-auth-js", "@emotion/react": "^11.11.3", "@emotion/server": "^11.11.0", diff --git a/sites/dapp-ui/src/lib/provider.ts b/sites/dapp-ui/src/lib/provider.ts index fcc9d5e..440fca5 100644 --- a/sites/dapp-ui/src/lib/provider.ts +++ b/sites/dapp-ui/src/lib/provider.ts @@ -1,6 +1,6 @@ import {decode} from 'cbor-x' import { decodeUnsignedTransaction } from "algosdk"; -import { IARC0001Transaction, ResponseMessage, Results, fromBase64Url } from "@algorandfoundation/algo-models/provider"; +import { IARC0001Transaction, ResponseMessage, Results, fromBase64Url } from "@algorandfoundation/provider"; export function attachEncodedSignature(address: string, txn: string, signature: string){ return decodeUnsignedTransaction(fromBase64Url(txn)) diff --git a/sites/dapp-ui/src/pages/connected.tsx b/sites/dapp-ui/src/pages/connected.tsx index 483b69b..7645571 100644 --- a/sites/dapp-ui/src/pages/connected.tsx +++ b/sites/dapp-ui/src/pages/connected.tsx @@ -18,7 +18,7 @@ import { makePaymentTxnWithSuggestedParamsFromObject, } from 'algosdk'; -import { RequestMessage, toSignTransactionsParamsRequestMessage, toBase64URL } from "@algorandfoundation/algo-models/provider"; +import { RequestMessage, toSignTransactionsParamsRequestMessage, toBase64URL } from "@algorandfoundation/provider"; import { SignalClient } from "@algorandfoundation/liquid-client"; import { attachSignedTransactionsFromResult,