Skip to content

Commit a451fe7

Browse files
KJES4colll78j-mueller
authored
Kat/send tokens (#71)
* fix cors and requestData errors * redeem mint wallet * add dynamic user info * add FE loading state * add lucid-evolution and wasm compatibility * submit endpoint * update ui to support lucid wallet loading * update lucid * recent changes * handle changes to profile switcher * add get wallet balance func * Fix hash * comment out output export * add send tokens func * add send from user func * add seize function * add alert updates * resolve merge issues * add call to look up blacklist * fix mint recipient and add error alert messages * implement table scroll and updates to alertbar * UI: Fix build * Add ui build to CI * Add recipient * wst-poc-cli: Add -threaded --------- Co-authored-by: colll78 <[email protected]> Co-authored-by: Jann Müller <[email protected]>
1 parent 4c4ee9d commit a451fe7

File tree

17 files changed

+779
-430
lines changed

17 files changed

+779
-430
lines changed

.github/workflows/ci-ui.yaml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: "ci-ui"
2+
on:
3+
pull_request:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-node@v4
14+
with:
15+
node-version: 23
16+
- run: |
17+
cd frontend
18+
npm install
19+
npm run export

frontend/.eslintrc.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"extends": ["next/core-web-vitals", "next/typescript"],
33
"rules": {
4-
"@next/next/no-page-custom-font": "off"
4+
"@next/next/no-page-custom-font": "off",
5+
"@typescript-eslint/no-explicit-any": "off"
56
}
67
}

frontend/package-lock.json

+270-224
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@
4242
"tsconfig-paths-webpack-plugin": "^4.2.0",
4343
"typescript": "^5"
4444
}
45-
}
45+
}

frontend/src/app/[username]/index.tsx

+79-16
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,120 @@
11
'use client';
2-
32
//React imports
4-
import React, { useState } from 'react';
3+
import React, { useEffect, useState } from 'react';
4+
5+
//Axios imports
6+
import axios from 'axios';
57

68
//Mui imports
79
import { Box, Typography } from '@mui/material';
810

911
//Local components
1012
import useStore from '../store/store';
13+
import { Accounts } from '../store/types';
14+
import { getWalletBalance, signAndSentTx } from '../utils/walletUtils';
1115
import WalletCard from '../components/Card';
1216
import WSTTextField from '../components/WSTTextField';
1317
import CopyTextField from '../components/CopyTextField';
1418

1519
export default function Profile() {
16-
const { currentUser, userA, userB, walletUser, setAlertStatus } = useStore();
20+
const { lucid, currentUser, mintAccount, changeAlertInfo, changeWalletAccountDetails } = useStore();
21+
const accounts = useStore((state) => state.accounts);
22+
23+
useEffect(() => {
24+
useStore.getState();
25+
// console.log("accounts changed:", accounts);
26+
}, [accounts]);
1727

1828
const getUserAccountDetails = () => {
1929
switch (currentUser) {
20-
case "User A": return userA;
21-
case "User B": return userB;
22-
case "Connected Wallet": return walletUser;
30+
case "User A": return accounts.userA;
31+
case "User B": return accounts.userB;
32+
case "Connected Wallet": return accounts.walletUser;
2333
};
2434
};
2535

2636
// temp state for each text field
27-
const [mintTokens, setMintTokens] = useState(0);
28-
const [recipientAddress, setRecipientAddress] = useState('address');
37+
const [sendTokenAmount, setMintTokens] = useState(0);
38+
const [sendRecipientAddress, setsendRecipientAddress] = useState('address');
39+
40+
const onSend = async () => {
41+
if (getUserAccountDetails()?.status === 'Frozen') {
42+
changeAlertInfo({
43+
severity: 'error',
44+
message: 'Cannot send WST with frozen account.',
45+
open: true,
46+
});
47+
return;
48+
}
49+
console.log('start sending tokens');
50+
changeAlertInfo({severity: 'info', message: 'Transaction processing', open: true,});
51+
const accountInfo = getUserAccountDetails();
52+
if (!accountInfo) {
53+
console.error("No valid send account found! Cannot send.");
54+
return;
55+
}
56+
lucid.selectWallet.fromSeed(accountInfo.mnemonic);
57+
const requestData = {
58+
asset_name: Buffer.from('WST', 'utf8').toString('hex'), // Convert "WST" to hex
59+
issuer: mintAccount.address,
60+
quantity: sendTokenAmount,
61+
recipient: sendRecipientAddress,
62+
sender: accountInfo.address,
63+
};
64+
try {
65+
const response = await axios.post(
66+
'/api/v1/tx/programmable-token/transfer',
67+
requestData,
68+
{
69+
headers: {
70+
'Content-Type': 'application/json;charset=utf-8',
71+
},
72+
}
73+
);
74+
console.log('Send response:', response.data);
75+
const tx = await lucid.fromTx(response.data.cborHex);
76+
await signAndSentTx(lucid, tx);
77+
await updateAccountBalance(sendRecipientAddress);
78+
await updateAccountBalance(accountInfo.address);
79+
changeAlertInfo({severity: 'success', message: 'Transaction sent successfully!', open: true,});
80+
} catch (error) {
81+
console.error('Send failed:', error);
82+
}
83+
};
2984

30-
const onSend = () => {
31-
console.log('send tokens');
32-
setAlertStatus(true);
85+
const updateAccountBalance = async (address: string) => {
86+
const newAccountBalance = await getWalletBalance(address);
87+
const walletKey = (Object.keys(accounts) as (keyof Accounts)[]).find(
88+
(key) => accounts[key].address === address
89+
);
90+
if (walletKey) {
91+
changeWalletAccountDetails(walletKey, {
92+
...accounts[walletKey],
93+
balance: newAccountBalance,
94+
});
95+
}
3396
};
3497

3598
const sendContent = <Box>
3699
<WSTTextField
37100
placeholder='0.0000'
38-
value={mintTokens}
101+
value={sendTokenAmount}
39102
onChange={(e) => setMintTokens(Number(e.target.value))}
40103
label="Number of Tokens to Send"
41104
fullWidth={true}
42105
/>
43106
<WSTTextField
44107
placeholder="address"
45-
value={recipientAddress}
46-
onChange={(e) => setRecipientAddress(e.target.value)}
108+
value={sendRecipientAddress}
109+
onChange={(e) => setsendRecipientAddress(e.target.value)}
47110
label="Recipient’s Address"
48111
fullWidth={true}
49112
/>
50113
</Box>;
51114

52115
const receiveContent = <Box>
53116
<CopyTextField
54-
value={getUserAccountDetails()?.address}
117+
value={getUserAccountDetails()?.address ?? ''}
55118
fullWidth={true}
56119
label="Your Address"
57120
/>
@@ -82,4 +145,4 @@ export default function Profile() {
82145
</div>
83146
</div>
84147
);
85-
}
148+
}

frontend/src/app/clientLayout.tsx

+11-10
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,26 @@ import NavDrawer from './components/NavDrawer';
99
//Local file
1010
import { ThemeModeProvider } from "./styles/themeContext";
1111
import "./styles/globals.css";
12-
import { makeLucid, getWalletFromSeed, getWalletBalance } from "./utils/walletUtils";
12+
import { makeLucid, getWalletFromSeed } from "./utils/walletUtils";
1313
import useStore from './store/store';
1414
import WSTAppBar from "./components/WSTAppBar";
15+
import AlertBar from './components/AlertBar';
1516

1617
export default function ClientLayout({ children }: { children: React.ReactNode }) {
17-
const { mintAccount, userA, userB, changeMintAccountDetails, changeWalletAAccountDetails, changeWalletBAccountDetails, setLucidInstance } = useStore();
18+
const { mintAccount, accounts, changeMintAccountDetails, changeWalletAccountDetails, setLucidInstance } = useStore();
1819

1920
useEffect(() => {
2021
const fetchUserWallets = async () => {
2122
try {
2223
// retrieve wallet info
2324
const mintAuthorityWallet = await getWalletFromSeed(mintAccount.mnemonic);
24-
const walletA = await getWalletFromSeed(userA.mnemonic);
25-
const walletB = await getWalletFromSeed(userB.mnemonic);
25+
const walletA = await getWalletFromSeed(accounts.userA.mnemonic);
26+
const walletB = await getWalletFromSeed(accounts.userB.mnemonic);
2627

27-
const mintStartBalance = await getWalletBalance(mintAuthorityWallet.address);
2828
// Update Zustand store with the initialized wallet information
29-
changeMintAccountDetails({ ...mintAccount, address: mintAuthorityWallet.address, balance: mintStartBalance});
30-
changeWalletAAccountDetails({ ...userA, address: walletA.address});
31-
changeWalletBAccountDetails({ ...userB, address: walletB.address,});
29+
changeMintAccountDetails({ ...mintAccount, address: mintAuthorityWallet.address});
30+
changeWalletAccountDetails('userA', { ...accounts.userA, address: walletA.address},);
31+
changeWalletAccountDetails('userB', { ...accounts.userB, address: walletB.address});
3232

3333
const initialLucid = await makeLucid();
3434
setLucidInstance(initialLucid);
@@ -39,9 +39,9 @@ export default function ClientLayout({ children }: { children: React.ReactNode }
3939
};
4040

4141
fetchUserWallets();
42-
}, []);
42+
},[]);
4343

44-
if(userB.address === '') {
44+
if(accounts.userB.address === '') {
4545
return <div className="mainLoadingContainer">
4646
<div className="mainLoader" />
4747
</div>;
@@ -54,6 +54,7 @@ export default function ClientLayout({ children }: { children: React.ReactNode }
5454
<WSTAppBar />
5555
<NavDrawer />
5656
<div className="contentSection">{children}</div>
57+
<AlertBar/>
5758
</main>
5859
</ThemeModeProvider>
5960
</AppRouterCacheProvider>

frontend/src/app/components/AlertBar.tsx

+6-11
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,22 @@ import Alert from '@mui/material/Alert';
88
//Local components
99
import useStore from '../store/store';
1010

11-
interface AlertBarProps {
12-
severity?: 'success' | 'error' | 'info' | 'warning';
13-
message: string;
14-
}
15-
16-
export default function AlertBar({severity = 'success', message}: AlertBarProps) {
17-
const { alertOpen, setAlertStatus } = useStore();
11+
export default function AlertBar() {
12+
const { alertInfo, changeAlertInfo } = useStore();
1813

1914
const handleClose = () => {
20-
setAlertStatus(false);
15+
changeAlertInfo({ ...alertInfo, open: false });
2116
};
2217

2318
return (
24-
<Snackbar open={alertOpen} anchorOrigin={{vertical: 'top', horizontal: 'center'}} >
19+
<Snackbar open={alertInfo.open} autoHideDuration={alertInfo.severity === 'success' ? 3000 : null} onClose={handleClose} anchorOrigin={{vertical: 'top', horizontal: 'center'}} >
2520
<Alert
26-
severity={severity}
21+
severity={alertInfo.severity}
2722
variant="filled"
2823
sx={{ width: '100%' }}
2924
onClose={handleClose}
3025
>
31-
{message}
26+
{alertInfo.message}
3227
</Alert>
3328
</Snackbar>
3429
);

frontend/src/app/components/ProfileSwitcher.tsx

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
'use client'
23
//React Imports
34
import * as React from 'react';
@@ -18,9 +19,7 @@ import { selectLucidWallet, getWalletBalance } from '../utils/walletUtils';
1819

1920
export default function ProfileSwitcher() {
2021
const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);
21-
const currentUser = useStore(state => state.currentUser);
22-
const walletUser = useStore(state => state.walletUser);
23-
const changeToLaceWallet = useStore(state => state.changeToLaceWallet);
22+
const { currentUser, accounts, changeWalletAccountDetails } = useStore();
2423
const lucid = useStore(state => state.lucid);
2524
const changeUserAccount = useStore(state => state.changeUserAccount);
2625
const router = useRouter();
@@ -64,8 +63,8 @@ export default function ProfileSwitcher() {
6463
await selectLucidWallet(lucid, "Lace");
6564
const userAddress = await lucid.wallet().address();
6665
const userBalance = await getWalletBalance(userAddress);
67-
changeToLaceWallet({
68-
...walletUser,
66+
changeWalletAccountDetails('walletUser', {
67+
...accounts.walletUser,
6968
address: userAddress,
7069
balance: userBalance,
7170
});

0 commit comments

Comments
 (0)