Skip to content

Commit 055c55c

Browse files
authored
Smart contract discord notifications (#93)
* Create README.md * Create PingPong.sol * ad * asd * Update README.md * Update README.md * asd * asd * adding retry logic if discord notification fails
1 parent 71489ab commit 055c55c

File tree

11 files changed

+11909
-2
lines changed

11 files changed

+11909
-2
lines changed

.vscode/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"editor.defaultFormatter": "ms-python.black-formatter"
55
},
66
"[typescript]": {
7-
"editor.defaultFormatter": "esbenp.prettier-vscode"
7+
"editor.defaultFormatter": "vscode.typescript-language-features"
88
}
9-
}
9+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# 🔁 BNB Chain → Discord PingPong Stream
2+
3+
This Firebase Functions project listens for `Pinged` and `Ponged` events from a smart contract via Moralis Streams, decodes logs using the ABI, and forwards only confirmed events to a Discord Webhook.
4+
5+
### 🛠️ Deployment Instructions
6+
7+
- Navigate to the `smart-contract` folder.
8+
- Deploy the `PingPong` contract using **Remix** or **Foundry** to your target network.
9+
10+
## 🚀 On-chain Deployment
11+
12+
The PingPong smart contract is deployed at:
13+
14+
👉 [0x52943bFb088221cd6E3181fbc19081A6B34be948](https://testnet.bscscan.com/address/0x52943bfb088221cd6e3181fbc19081a6b34be948#code)
15+
16+
17+
## 🔔 Notifications
18+
19+
You can view real-time Ping/Pong event notifications in the Discord channel **#onchain-ping-pong-notifications**:
20+
21+
👉👉👉 https://discord.gg/xVzRZ9xjYm
22+
23+
### Example Notifications
24+
25+
> **PingPongNotifier • APP — 7:36 PM**
26+
> 🏓 **Ping event received!**
27+
> 👤 Sender: `0xc02024B4d446E91253Da8549805553Ac34F9D572`
28+
> 🔢 Ping #: 2
29+
> 🕒 Time: *July 11, 2025 at 7:35 PM*
30+
31+
> **PingPongNotifier • APP — 7:48 PM**
32+
> 🏓 **Pong event received!**
33+
> 👤 Sender: `0xc02024B4d446E91253Da8549805553Ac34F9D572`
34+
> 🔢 Pong #: 2
35+
> 🕒 Time: *July 11, 2025 at 7:47 PM*
36+
37+
38+
39+
## ✅ Features
40+
41+
- **Two HTTPS Functions:**
42+
- `/pingedStream` for `Pinged` events
43+
- `/pongedStream` for `Ponged` events
44+
- Processes only `confirmed: true` events
45+
- Decodes logs with Moralis SDK and ABI
46+
- Forwards formatted messages to Discord Webhook
47+
- Supports `.env` for local development and `firebase functions:config` for production
48+
49+
## 📁 Project Structure
50+
51+
```
52+
functions/
53+
├── src/
54+
│ └── index.ts # Function logic
55+
├── .env # Local development secrets
56+
├── package.json
57+
├── tsconfig.json
58+
└── README.md
59+
```
60+
61+
## ⚙️ Setup & Deployment
62+
63+
### 1. Install dependencies
64+
65+
```bash
66+
cd functions
67+
npm install
68+
```
69+
70+
### 2. Configure Discord Webhook URL
71+
72+
#### Local development via `.env`
73+
74+
1. Create `functions/.env` with:
75+
```env
76+
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/XXX/YYY"
77+
```
78+
2. In `src/index.ts`, at the top add:
79+
```ts
80+
import * as dotenv from "dotenv";
81+
dotenv.config();
82+
83+
const DISCORD_WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL;
84+
if (!DISCORD_WEBHOOK_URL) throw new Error("Missing DISCORD_WEBHOOK_URL");
85+
```
86+
87+
#### Production via Firebase config
88+
89+
1. Run:
90+
```bash
91+
firebase functions:config:set discord.webhook_url="https://discord.com/api/webhooks/XXX/YYY"
92+
```
93+
2. In `src/index.ts`, replace loading with:
94+
```ts
95+
import * as functions from "firebase-functions";
96+
97+
const DISCORD_WEBHOOK_URL = functions.config().discord.webhook_url;
98+
if (!DISCORD_WEBHOOK_URL) throw new Error("Missing config.discord.webhook_url");
99+
```
100+
101+
### 3. Deploy
102+
103+
```bash
104+
firebase deploy --only functions
105+
```
106+
107+
### 4. Local testing
108+
109+
```bash
110+
firebase emulators:start
111+
```
112+
113+
Send a POST request with JSON payload including `"confirmed": true` to:
114+
115+
- `http://localhost:5001/<PROJECT_ID>/us-central1/pingedStream`
116+
- `http://localhost:5001/<PROJECT_ID>/us-central1/pongedStream`
117+
118+
## 🔗 Moralis Stream Setup
119+
120+
1. Log in at [Moralis Admin](https://admin.moralis.io/)**Streams****New Stream**
121+
2. Create two streams:
122+
👉 USER YOUR CREATED WEBHOOK FIREBASE URLS
123+
124+
- **Pinged** → Webhook URL: `https://<YOUR_APP>.cloudfunctions.net/pingedStream`
125+
- **Ponged** → Webhook URL: `https://<YOUR_APP>.cloudfunctions.net/pongedStream`
126+
3. In each stream’s **ABI** section, paste the corresponding JSON:
127+
128+
<details>
129+
<summary><strong>Pinged ABI</strong></summary>
130+
131+
```json
132+
[
133+
{
134+
"anonymous": false,
135+
"inputs": [
136+
{ "indexed": true, "internalType": "address", "name": "sender", "type": "address" },
137+
{ "indexed": false, "internalType": "uint256", "name": "pingCount","type": "uint256" },
138+
{ "indexed": false, "internalType": "uint256", "name": "timestamp","type": "uint256" }
139+
],
140+
"name": "Pinged",
141+
"type": "event"
142+
}
143+
]
144+
```
145+
</details>
146+
147+
<details>
148+
<summary><strong>Ponged ABI</strong></summary>
149+
150+
```json
151+
[
152+
{
153+
"anonymous": false,
154+
"inputs": [
155+
{ "indexed": true, "internalType": "address", "name": "sender", "type": "address" },
156+
{ "indexed": false, "internalType": "uint256", "name": "pongCount", "type": "uint256" },
157+
{ "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" }
158+
],
159+
"name": "Ponged",
160+
"type": "event"
161+
}
162+
]
163+
```
164+
</details>
165+
166+
4. Enable **only confirmed transactions will be sent by the webhook**.
167+
168+
## 🧩 Tech Stack
169+
170+
- **Solidity**
171+
- **Firebase Functions v2** (Node.js 18+)
172+
- **TypeScript**
173+
- **Moralis SDK** (`@moralisweb3/core`, `@moralisweb3`)
174+
- **Axios**
175+
- **Discord Webhook API**
176+
177+
## 📜 License
178+
179+
MIT
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
contract PingPong {
5+
uint256 public pingCount;
6+
uint256 public pongCount;
7+
8+
enum LastMove {
9+
None,
10+
Ping,
11+
Pong
12+
}
13+
LastMove public lastMove = LastMove.None;
14+
15+
event Pinged(address indexed sender, uint256 pingCount, uint256 timestamp);
16+
event Ponged(address indexed sender, uint256 pongCount, uint256 timestamp);
17+
18+
error InvalidMove(string reason);
19+
20+
function ping() external {
21+
if (lastMove == LastMove.Ping)
22+
revert InvalidMove("Cannot ping twice in a row");
23+
24+
pingCount += 1;
25+
lastMove = LastMove.Ping;
26+
27+
emit Pinged(msg.sender, pingCount, block.timestamp);
28+
}
29+
30+
function pong() external {
31+
if (lastMove != LastMove.Ping) revert InvalidMove("Must ping before pong");
32+
33+
pongCount += 1;
34+
lastMove = LastMove.Pong;
35+
36+
emit Ponged(msg.sender, pongCount, block.timestamp);
37+
}
38+
39+
function getGameStatus()
40+
external
41+
view
42+
returns (uint256 totalPings, uint256 totalPongs, string memory nextMove)
43+
{
44+
totalPings = pingCount;
45+
totalPongs = pongCount;
46+
47+
if (lastMove == LastMove.None || lastMove == LastMove.Pong) {
48+
nextMove = "ping";
49+
} else {
50+
nextMove = "pong";
51+
}
52+
}
53+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/YOUR_WEBHOOK"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// eslint-disable-next-line @typescript-eslint/no-var-requires
2+
const path = require("path");
3+
4+
module.exports = {
5+
root: true,
6+
env: {
7+
es6: true,
8+
node: true,
9+
},
10+
extends: [
11+
"eslint:recommended",
12+
"plugin:import/errors",
13+
"plugin:import/warnings",
14+
"plugin:import/typescript",
15+
"google",
16+
"plugin:@typescript-eslint/recommended",
17+
],
18+
parser: "@typescript-eslint/parser",
19+
parserOptions: {
20+
tsconfigRootDir: __dirname,
21+
project: ["./tsconfig.json"],
22+
sourceType: "module",
23+
},
24+
ignorePatterns: [
25+
"/lib/**/*",
26+
"/generated/**/*",
27+
".eslintrc.js", // ✅ Exclude this config file from linting
28+
],
29+
plugins: [
30+
"@typescript-eslint",
31+
"import",
32+
],
33+
rules: {
34+
"quotes": ["error", "double"],
35+
"import/no-unresolved": 0,
36+
"indent": ["error", 2],
37+
"@typescript-eslint/no-var-requires": "off",
38+
"max-len": ["warn", { "code": 100 }],
39+
},
40+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Compiled JavaScript files
2+
lib/**/*.js
3+
lib/**/*.js.map
4+
5+
# TypeScript v1 declaration files
6+
typings/
7+
8+
# Node.js dependency directory
9+
node_modules/
10+
*.local

0 commit comments

Comments
 (0)