Skip to content

Commit ab9fe09

Browse files
Audityzer Botclaude
andcommitted
feat: add Superfluid streaming payments integration — contracts, subgraph, monitoring, CI/CD
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7fa0fb7 commit ab9fe09

18 files changed

Lines changed: 713 additions & 0 deletions
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Audityzer Superfluid Deploy
2+
3+
on:
4+
push:
5+
branches: [main, safe-improvements]
6+
paths:
7+
- 'superfluid/**'
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-node@v4
15+
with: { node-version: '20' }
16+
- run: cd superfluid && npm ci
17+
- run: cd superfluid && npx hardhat compile
18+
- run: cd superfluid && npx hardhat test
19+
20+
deploy-subgraph:
21+
needs: test
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v4
25+
- run: npm install -g @graphprotocol/graph-cli
26+
- run: cd superfluid/subgraph && graph auth --studio ${{ secrets.GRAPH_ACCESS_TOKEN }}
27+
- run: cd superfluid/subgraph && graph codegen && graph build
28+
- run: |
29+
cd superfluid/subgraph
30+
graph deploy --studio audityzer-streams-op \
31+
--version-label v${{ github.run_number }}.0.0
32+
33+
deploy-contracts:
34+
needs: test
35+
runs-on: ubuntu-latest
36+
if: github.ref == 'refs/heads/main'
37+
steps:
38+
- uses: actions/checkout@v4
39+
- run: cd superfluid && npm ci
40+
- run: cd superfluid && npx hardhat run scripts/deploy-rewards-macro.ts --network op-mainnet
41+
env:
42+
OP_MAINNET_RPC: ${{ secrets.OP_MAINNET_RPC }}
43+
DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PK }}

superfluid/.env.example

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Graph Protocol
2+
GRAPH_API_KEY=
3+
GRAPH_ACCESS_TOKEN=
4+
5+
# Alchemy
6+
ALCHEMY_WS_KEY=
7+
ALCHEMY_SEPOLIA_KEY=
8+
9+
# Contract Addresses
10+
AUDITYZER_ADDR=
11+
DEPLOYER_PRIVATE_KEY=
12+
13+
# RPC
14+
OP_MAINNET_RPC=https://mainnet.optimism.io
15+
OPTIMISTIC_ETHERSCAN_API_KEY=
16+
17+
# Subgraph
18+
SUPERFLUID_OP_SUBGRAPH_ID=

superfluid/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Audityzer × Superfluid Streaming Payments Integration
2+
3+
Superfluid streaming payments integration for the Audityzer Web3 security platform. Enables real-time, continuous reward streams to security auditors and contributors.
4+
5+
## Architecture
6+
7+
- **RewardsMacro Contract** — Uses Superfluid's MacroForwarder (`IUserDefinedMacro`) to batch-create reward streams in a single transaction
8+
- **Subgraph** — Indexes `FlowUpdated` events on Optimism Sepolia for stream tracking and TVL monitoring
9+
- **Monitoring Service** — Real-time WebSocket listener + 120s polling for balance and flow rate metrics
10+
- **CI/CD** — GitHub Actions pipeline for testing, subgraph deployment, and contract deployment
11+
12+
## Quick Start
13+
14+
```bash
15+
# Install dependencies
16+
npm install
17+
18+
# Copy env and configure
19+
cp .env.example .env
20+
21+
# Compile contracts
22+
npm run compile
23+
24+
# Run tests
25+
npm test
26+
27+
# Deploy to Optimism Sepolia
28+
npm run deploy:sepolia
29+
```
30+
31+
## Scripts
32+
33+
| Command | Description |
34+
|---------|-------------|
35+
| `npm run compile` | Compile Solidity contracts |
36+
| `npm test` | Run Hardhat tests |
37+
| `npm run deploy:sepolia` | Deploy RewardsMacro to OP Sepolia |
38+
| `npm run deploy:mainnet` | Deploy RewardsMacro to OP Mainnet |
39+
| `npm run stream:test` | Create a test stream on Sepolia |
40+
| `npm run monitor` | Start the monitoring service |
41+
| `npm run subgraph:codegen` | Generate subgraph types |
42+
| `npm run subgraph:build` | Build the subgraph |
43+
| `npm run subgraph:deploy:dev` | Deploy subgraph to Studio |
44+
45+
## Networks
46+
47+
| Network | Chain ID | RPC |
48+
|---------|----------|-----|
49+
| Optimism Sepolia | 11155420 | https://sepolia.optimism.io |
50+
| Optimism Mainnet | 10 | https://mainnet.optimism.io |
51+
52+
## Key Addresses
53+
54+
- **MacroForwarder**: `0xcfA132E353cB4E398080B9700609bb008eceB125` (same on all networks)
55+
56+
## Integration Notes
57+
58+
- All addresses are lowercased in subgraph queries
59+
- Production subgraph queries use `gateway.thegraph.com`
60+
- SDK initialization uses auto-resolve (no `protocolReleaseVersion` or `resolverAddress`)
61+
- Monitoring poll interval: 120s minimum (free API quota compliance)
62+
- Use `accountTokenSnapshots` (not `streamAccounts`) for subgraph queries

superfluid/config/addresses.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"macroForwarder": "0xcfA132E353cB4E398080B9700609bb008eceB125",
3+
"rewardsMacro": {
4+
"opSepolia": "",
5+
"opMainnet": ""
6+
},
7+
"audityzerProxy": {
8+
"opSepolia": "",
9+
"opMainnet": ""
10+
}
11+
}

superfluid/config/subgraph.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const SUBGRAPH_URL =
2+
`https://gateway.thegraph.com/api/${process.env.GRAPH_API_KEY}/subgraphs/id/${process.env.SUPERFLUID_OP_SUBGRAPH_ID}`;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {ISuperfluid, ISuperToken} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
5+
import {IConstantFlowAgreementV1} from "@superfluid-finance/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol";
6+
import {SuperTokenV1Library} from "@superfluid-finance/ethereum-contracts/contracts/apps/SuperTokenV1Library.sol";
7+
import {MacroForwarder, IUserDefinedMacro} from "@superfluid-finance/ethereum-contracts/contracts/utils/MacroForwarder.sol";
8+
9+
contract RewardsMacro is IUserDefinedMacro {
10+
using SuperTokenV1Library for ISuperToken;
11+
12+
struct RewardStream {
13+
address receiver;
14+
int96 flowRate;
15+
}
16+
17+
function buildBatchOperations(
18+
ISuperfluid host,
19+
bytes memory params,
20+
address msgSender
21+
) external view override returns (ISuperfluid.Operation[] memory operations) {
22+
(ISuperToken token, RewardStream[] memory streams) = abi.decode(
23+
params,
24+
(ISuperToken, RewardStream[])
25+
);
26+
27+
operations = new ISuperfluid.Operation[](streams.length);
28+
29+
for (uint256 i = 0; i < streams.length; i++) {
30+
// Create or update flow for each reward recipient
31+
bytes memory callData = abi.encodeCall(
32+
IConstantFlowAgreementV1.createFlow,
33+
(token, streams[i].receiver, streams[i].flowRate, new bytes(0))
34+
);
35+
operations[i] = ISuperfluid.Operation({
36+
operationType: 201, // OPERATION_TYPE_SUPERFLUID_CALL_AGREEMENT
37+
target: address(host.getAgreementClass(
38+
keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1")
39+
)),
40+
data: abi.encode(callData, new bytes(0))
41+
});
42+
}
43+
}
44+
45+
function postCheck(
46+
ISuperfluid host,
47+
bytes memory params,
48+
address msgSender
49+
) external view override {
50+
// Optional: verify all streams were created successfully
51+
}
52+
}

superfluid/hardhat.config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { HardhatUserConfig } from "hardhat/config";
2+
import "@nomicfoundation/hardhat-toolbox";
3+
import * as dotenv from "dotenv";
4+
dotenv.config();
5+
6+
const config: HardhatUserConfig = {
7+
solidity: {
8+
version: "0.8.24",
9+
settings: { optimizer: { enabled: true, runs: 200 } },
10+
},
11+
networks: {
12+
"op-sepolia": {
13+
url: "https://sepolia.optimism.io",
14+
chainId: 11155420,
15+
accounts: process.env.DEPLOYER_PRIVATE_KEY ? [process.env.DEPLOYER_PRIVATE_KEY] : [],
16+
},
17+
"op-mainnet": {
18+
url: process.env.OP_MAINNET_RPC || "https://mainnet.optimism.io",
19+
chainId: 10,
20+
accounts: process.env.DEPLOYER_PRIVATE_KEY ? [process.env.DEPLOYER_PRIVATE_KEY] : [],
21+
},
22+
},
23+
etherscan: {
24+
apiKey: { optimisticEthereum: process.env.OPTIMISTIC_ETHERSCAN_API_KEY || "" },
25+
},
26+
};
27+
export default config;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Framework } from "@superfluid-finance/sdk-core";
2+
import { ethers } from "ethers";
3+
4+
const CONFIG = {
5+
chainId: 10,
6+
wsUrl: `wss://opt-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_WS_KEY}`,
7+
audityzerAddr: process.env.AUDITYZER_ADDR!.toLowerCase(),
8+
pollIntervalMs: 120_000,
9+
};
10+
11+
let provider: ethers.WebSocketProvider;
12+
let sf: Framework;
13+
14+
async function initializeMonitoring() {
15+
try {
16+
provider = new ethers.WebSocketProvider(CONFIG.wsUrl);
17+
sf = await Framework.create({ chainId: CONFIG.chainId, provider });
18+
const usdcx = await sf.loadSuperToken("USDCx");
19+
20+
setInterval(async () => {
21+
const [rtb, netFlow] = await Promise.all([
22+
usdcx.realtimeBalanceOf({ account: CONFIG.audityzerAddr, providerOrSigner: provider }),
23+
sf.cfaV1.getNetFlow({ superToken: usdcx.address, account: CONFIG.audityzerAddr, providerOrSigner: provider }),
24+
]);
25+
console.log({
26+
availableBalance: ethers.formatEther(rtb.availableBalance),
27+
deposit: ethers.formatEther(rtb.deposit),
28+
netFlowRateWeiPerSec: netFlow,
29+
});
30+
}, CONFIG.pollIntervalMs);
31+
32+
const cfaContract = new ethers.Contract(
33+
sf.settings.config.cfaV1Address,
34+
["event FlowUpdated(address indexed token, address indexed sender, address indexed receiver, int96 flowRate, int256 totalSenderFlowRate, int256 totalReceiverFlowRate, bytes userData)"],
35+
provider
36+
);
37+
38+
cfaContract.on("FlowUpdated", (token: string, sender: string, receiver: string, flowRate: bigint) => {
39+
if (receiver.toLowerCase() === CONFIG.audityzerAddr) {
40+
console.log(`Stream update: ${sender} → rate ${flowRate}`);
41+
}
42+
});
43+
44+
provider.websocket.on("close", () => {
45+
console.warn("WS disconnected — reconnecting in 5s...");
46+
setTimeout(initializeMonitoring, 5000);
47+
});
48+
} catch (err) {
49+
console.error("Monitor init failed:", err);
50+
setTimeout(initializeMonitoring, 10_000);
51+
}
52+
}
53+
54+
initializeMonitoring();

superfluid/package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@audityzer/superfluid-integration",
3+
"version": "1.0.0",
4+
"description": "Audityzer × Superfluid streaming payments integration",
5+
"scripts": {
6+
"compile": "hardhat compile",
7+
"test": "hardhat test",
8+
"deploy:sepolia": "hardhat run scripts/deploy-rewards-macro.ts --network op-sepolia",
9+
"deploy:mainnet": "hardhat run scripts/deploy-rewards-macro.ts --network op-mainnet",
10+
"stream:test": "ts-node scripts/create-test-stream.ts",
11+
"monitor": "ts-node monitoring/monitoring.ts",
12+
"subgraph:codegen": "cd subgraph && graph codegen",
13+
"subgraph:build": "cd subgraph && graph build",
14+
"subgraph:deploy:dev": "cd subgraph && graph deploy --studio audityzer-streams-op-sepolia",
15+
"test:e2e": "hardhat test --network op-sepolia"
16+
},
17+
"dependencies": {
18+
"@superfluid-finance/sdk-core": "^0.9.0",
19+
"@superfluid-finance/ethereum-contracts": "^1.10.0",
20+
"ethers": "^6.13.0",
21+
"@graphprotocol/graph-cli": "^0.80.0",
22+
"@graphprotocol/graph-ts": "^0.35.0",
23+
"dotenv": "^16.4.0"
24+
},
25+
"devDependencies": {
26+
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
27+
"hardhat": "^2.22.0",
28+
"typescript": "^5.5.0",
29+
"ts-node": "^10.9.2",
30+
"@types/node": "^22.0.0"
31+
}
32+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Framework } from "@superfluid-finance/sdk-core";
2+
import { ethers } from "ethers";
3+
4+
const AUDITYZER_TEST = process.env.AUDITYZER_ADDR!.toLowerCase();
5+
6+
async function main() {
7+
const provider = new ethers.JsonRpcProvider(
8+
`https://opt-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_SEPOLIA_KEY}`
9+
);
10+
const signer = new ethers.Wallet(process.env.DEPLOYER_PK!, provider);
11+
const sf = await Framework.create({ chainId: 11155420, provider });
12+
const usdcx = await sf.loadSuperToken("USDCx");
13+
14+
const createOp = sf.cfaV1.createFlow({
15+
sender: await signer.getAddress(),
16+
receiver: AUDITYZER_TEST,
17+
superToken: usdcx.address,
18+
flowRate: "1000000",
19+
});
20+
21+
const tx = await createOp.exec(signer);
22+
await tx.wait();
23+
console.log("Stream created:", tx.hash);
24+
}
25+
main();

0 commit comments

Comments
 (0)