Skip to content

Commit

Permalink
feat(notification-system): implemented a basic level notification cha…
Browse files Browse the repository at this point in the history
…nnel between client and server via websockets
  • Loading branch information
Aman-zishan committed Dec 17, 2023
1 parent e0f1fe6 commit 8364bc2
Show file tree
Hide file tree
Showing 15 changed files with 251 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# sDAO WITH CHAINHOOKS 🔗🪝

<img width="1440" alt="Screenshot 2023-12-14 at 5 34 15 AM" src="./assets/Untitled-2023-12-05-1121.png">
<img width="1440" alt="Screenshot 2023-12-14 at 5 34 15 AM" src="./assets/architecture.png">



Expand Down
Binary file removed assets/Untitled-2023-12-05-1121.png
Binary file not shown.
Binary file added assets/architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
22 changes: 22 additions & 0 deletions chainhooks/proposal-conclude.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"chain": "stacks",
"uuid": "1",
"name": "proposal conclude",
"version": 1,
"networks": {
"devnet": {
"if_this": {
"scope": "contract_call",
"contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.proposal-voting",
"method": "conclude"
},
"then_that": {
"http_post": {
"url": "http://localhost:3000/api/chainhook/proposal-conclude",
"authorization_header": "Bearer 12345"
}
},
"start_block": 138339
}
}
}
2 changes: 1 addition & 1 deletion chainhooks/proposal-submission.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"chain": "stacks",
"uuid": "1",
"name": "Milestone extension proposal",
"name": "new proposal",
"version": 1,
"networks": {
"devnet": {
Expand Down
22 changes: 22 additions & 0 deletions chainhooks/proposal-vote.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"chain": "stacks",
"uuid": "1",
"name": "proposal vote",
"version": 1,
"networks": {
"devnet": {
"if_this": {
"scope": "contract_call",
"contract_identifier": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.proposal-voting",
"method": "vote"
},
"then_that": {
"http_post": {
"url": "http://localhost:3000/api/chainhook/proposal-vote",
"authorization_header": "Bearer 12345"
}
},
"start_block": 138339
}
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@
"lucide-react": "^0.294.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-use-websocket": "^4.5.0",
"sonner": "^1.2.4",
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"ws": "^8.15.1"
},
"devDependencies": {
"@commitlint/cli": "^17.4.3",
Expand Down
128 changes: 123 additions & 5 deletions sdao-api/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,35 @@
const express = require('express');
const app = express();
app.use(express.json());
const WebSocket = require('ws');
const http = require('http');

require('dotenv').config();
const createClient = require('@supabase/supabase-js').createClient;

const supabase = createClient(
process.env.VITE_SUPABASE_PROJECT_URL,
process.env.VITE_SUPABASE_ANON_KEY
);
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// Function to send message to all connected WebSocket clients
const broadcast = (data) => {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
console.log('SENDING MESSAGE TO CLIENT :::', data);
client.send(JSON.stringify(data));
}
});
};

app.post('/api/chainhook/bootstrap/stx-transfer', async (req, res) => {
console.log('=====================================');
console.log('STX INITIAL TRANSFER');
console.log('=====================================');
const events = req.body;
let chainState = false;
let currentStep = 0;
const { data: currentData, error: currentDataError } = await supabase
.from('bootstrap')
Expand All @@ -38,6 +54,7 @@ app.post('/api/chainhook/bootstrap/stx-transfer', async (req, res) => {
// Log the operation
console.log(operation.status);
if (operation.status === 'SUCCESS') {
chainState = true;
console.log('successful STX transfer');
const { error } = await supabase.from('bootstrap').upsert({
id: 1,
Expand All @@ -53,8 +70,14 @@ app.post('/api/chainhook/bootstrap/stx-transfer', async (req, res) => {
});
}

if (chainState) {
broadcast({ message: 'STX transfer successful!', type: 'success' });
} else {
broadcast({ message: 'STX transfer error!', type: 'error' });
}

// Send a response back to Chainhook to acknowledge receipt of the event
res.status(200).send({ message: 'STX transfer done!' });
res.status(200).send({ message: 'message received' });
});

app.post('/api/chainhook/bootstrap/construct-call', async (req, res) => {
Expand All @@ -63,6 +86,7 @@ app.post('/api/chainhook/bootstrap/construct-call', async (req, res) => {
console.log('CONSTRUCT CALL');
console.log('=====================================');
console.log(events);
let chainState = false;
const { data: currentData, error: currentDataError } = await supabase
.from('bootstrap')
.select('current_step')
Expand All @@ -82,6 +106,7 @@ app.post('/api/chainhook/bootstrap/construct-call', async (req, res) => {
console.log(operation);
console.log(operation.status);
if (operation.status === 'SUCCESS') {
chainState = true;
const { data: responseData, error } = await supabase

Check warning on line 110 in sdao-api/server.js

View workflow job for this annotation

GitHub Actions / Lint

'responseData' is assigned a value but never used
.from('bootstrap')
.upsert({
Expand All @@ -100,22 +125,34 @@ app.post('/api/chainhook/bootstrap/construct-call', async (req, res) => {
console.log('Current step is 3 or higher, not updating to 2');
}

if (chainState) {
broadcast({
message: 'bootstrap construct function invoked!',
type: 'success'
});
} else {
broadcast({ message: 'something went wrong!', type: 'error' });
}

// Send a response back to Chainhook to acknowledge receipt of the event
res.status(200).send({ message: 'contruct function called!' });
res.status(200).send({ message: 'message received' });
});

app.post('/api/chainhook/proposal-submission', async (req, res) => {
const events = req.body;
console.log('=====================================');
console.log('EXTENSION PROPOSAL');
console.log('PROPOSAL SUBMISSION');
console.log('=====================================');
console.log(events);

let chainState = false;

events.apply.forEach(async (item) => {
// Loop through each transaction in the item
console.log('TX ITEM LENGTH::', item.transactions.length);
item.transactions.forEach(async (transaction) => {
if (transaction.metadata.success) {
chainState = true;
console.log('EVENTS::', transaction.metadata.description);
const regex = /\((.*?)\)/;
const match = transaction.metadata.description.match(regex);
Expand Down Expand Up @@ -150,10 +187,91 @@ app.post('/api/chainhook/proposal-submission', async (req, res) => {
});
});

if (chainState) {
broadcast({
message: 'new proposal submitted!',
type: 'success'
});
} else {
broadcast({ message: 'something went wrong!', type: 'error' });
}

// Send a response back to Chainhook to acknowledge receipt of the event
res.status(200).send({ message: 'message received' });
});

app.post('/api/chainhook/proposal-vote', async (req, res) => {
const events = req.body;
console.log('=====================================');
console.log('PROPOSAL VOTE');
console.log('=====================================');
console.log(events);

let chainState = false;
let voteNumber = 0;

events.apply.forEach(async (item) => {
// Loop through each transaction in the item
console.log('TX ITEM LENGTH::', item.transactions.length);
item.transactions.forEach(async (transaction) => {
if (transaction.metadata.success) {
console.log('EVENTS::', transaction.metadata.description);
chainState = true;
const match = transaction.metadata.description.match(/::vote\(u(\d+),/);
if (match && match[1]) {
voteNumber = parseInt(match[1], 10);
}
}
});
});

if (chainState) {
broadcast({
message: `${voteNumber} votes cast on proposal`,
type: 'success'
});
} else {
broadcast({ message: 'something went wrong!', type: 'error' });
}

// Send a response back to Chainhook to acknowledge receipt of the event
res.status(200).send({ message: 'message received' });
});

app.post('/api/chainhook/proposal-conclude', async (req, res) => {
const events = req.body;
console.log('=====================================');
console.log('PROPOSAL CONCLUDE');
console.log('=====================================');
console.log(events);

let chainState = false;

events.apply.forEach(async (item) => {
// Loop through each transaction in the item
console.log('TX ITEM LENGTH::', item.transactions.length);
item.transactions.forEach(async (transaction) => {
if (transaction.metadata.success) {
console.log('EVENTS::', transaction.metadata.description);

chainState = true;
}
});
});

if (chainState) {
broadcast({
message: ' proposal concluded!',
type: 'success'
});
} else {
broadcast({ message: 'something went wrong!', type: 'error' });
}

// Send a response back to Chainhook to acknowledge receipt of the event
res.status(200).send({ message: 'milestone extension proposed!' });
res.status(200).send({ message: 'message received' });
});

app.listen(3000, () => {
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
2 changes: 1 addition & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ createRoot(document.getElementById('root') as HTMLElement).render(
network={devnet}
>
<React.StrictMode>
<Toaster richColors />
<Toaster richColors position="top-right" />
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
Expand Down
17 changes: 17 additions & 0 deletions src/pages/bootstrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { createClient } from '@supabase/supabase-js';
import React, { useEffect } from 'react';
import { toast } from 'sonner';
import LeftMenu from '../components/leftMenu';
import useWebSocket from 'react-use-websocket';

const supabase = createClient(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Expand All @@ -30,6 +31,22 @@ const Bootstrap = () => {
const { openStxTokenTransfer } = useOpenStxTokenTransfer();
const { openContractCall } = useOpenContractCall();

const { lastMessage } = useWebSocket('ws://localhost:3000');

useEffect(() => {
if (lastMessage !== null) {
const data = JSON.parse(lastMessage.data);
console.log('Message from Server:', data.message);
if (data.type === 'success') {
toast.success(data.message);
} else if (data.type === 'error') {
toast.error(data.message);
}

// Handle the message as needed (e.g., update state, UI)
}
}, [lastMessage]);

useEffect(() => {
fetchDataFromSupabase();
}, []);
Expand Down
19 changes: 18 additions & 1 deletion src/pages/newClaimProposal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
stringAsciiCV,
stringUtf8CV
} from '@stacks/transactions';
import React from 'react';
import React, { useEffect } from 'react';
import { toast } from 'sonner';
import CodeEditor from '../components/codeEditor';
import LeftMenu from '../components/leftMenu';
import useWebSocket from 'react-use-websocket';

const initialContractBoilerplate = `;; This is a boilerplate contract for a grant milestone claim proposal\n
(impl-trait .proposal-trait.proposal-trait)
Expand All @@ -32,6 +33,22 @@ const NewClaimProposal = () => {
const [response, setResponse] = React.useState('');
const [deployed, setDeployed] = React.useState(false);

const { lastMessage } = useWebSocket('ws://localhost:3000');

useEffect(() => {
if (lastMessage !== null) {
const data = JSON.parse(lastMessage.data);
console.log('Message from Server:', data.message);
if (data.type === 'success') {
toast.success(data.message);
} else if (data.type === 'error') {
toast.error(data.message);
}

// Handle the message as needed (e.g., update state, UI)
}
}, [lastMessage]);

const { openContractCall, isRequestPending: isProposeReqPending } =
useOpenContractCall();

Expand Down
20 changes: 19 additions & 1 deletion src/pages/newGrantProposal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
stringAsciiCV,
stringUtf8CV
} from '@stacks/transactions';
import React from 'react';
import React, { useEffect } from 'react';
import { toast } from 'sonner';
import CodeEditor from '../components/codeEditor';
import LeftMenu from '../components/leftMenu';
import useWebSocket from 'react-use-websocket';

const initialContractBoilerplate = `;; This is a boilerplate contract for a grant proposal
Expand Down Expand Up @@ -39,6 +40,23 @@ const NewGrantProposal = () => {

const { openContractDeploy, isRequestPending } = useOpenContractDeploy();
const { stxAddress } = useAccount();

const { lastMessage } = useWebSocket('ws://localhost:3000');

useEffect(() => {
if (lastMessage !== null) {
const data = JSON.parse(lastMessage.data);
console.log('Message from Server:', data.message);
if (data.type === 'success') {
toast.success(data.message);
} else if (data.type === 'error') {
toast.error(data.message);
}

// Handle the message as needed (e.g., update state, UI)
}
}, [lastMessage]);

const handleCodeChange = (code) => {
console.log('Code in parent component:', code);
setCode(code);
Expand Down
Loading

0 comments on commit 8364bc2

Please sign in to comment.