Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 181 additions & 4 deletions apps/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import express from "express"
import { authMiddleware } from "./middleware";
import { prismaClient } from "db/client";
import cors from "cors";
import { Transaction, SystemProgram, Connection } from "@solana/web3.js";
import { SystemProgram, Connection, PublicKey, VersionedTransaction, TransactionMessage, Keypair, clusterApiUrl } from "@solana/web3.js";
import { redis } from "cache/client";


const connection = new Connection("https://api.mainnet-beta.solana.com");
const connection = new Connection(clusterApiUrl("devnet"));
const app = express();

app.use(cors());
Expand Down Expand Up @@ -84,7 +84,184 @@ app.delete("/api/v1/website/", authMiddleware, async (req, res) => {
})

app.post("/api/v1/payout/:validatorId", async (req, res) => {


const validatorId = req.params.validatorId;

const validator = await prismaClient.validator.findFirst({
where:{
id: validatorId,
}
});

if(!validator){
res.json({
message: "Validator not found"
})
return;
}

if(validator.payoutLocked){
res.json({
message: "Payout is locked, Please wait for some time before attempting to payout again"
})
return;
}

await redis.lpush("payouts", validatorId);

res.json({
message: "Payout queued successfully"
})

return;

})


app.listen(8080);


async function main(){
// Avoid Rate Limiting
setInterval(async ()=>{
await sendPayouts();
await verifyTxs();
},3000)
}

main();

async function sendPayouts(){

const validatorId = await redis.rpop("payouts");

try {

if(validatorId){

const validator = await prismaClient.validator.findFirst({
where:{
id: validatorId,
}
});

if(validator && !validator.payoutLocked){

await prismaClient.validator.update({
where: {
id: validatorId
},
data: {
payoutLocked: true
}
});

// create tx and push it to txVerification queue

const keypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(process.env.PRIVATE_KEY!)));

const lamports = validator.pendingPayouts;

const transferIx = SystemProgram.transfer({
fromPubkey: keypair.publicKey,
toPubkey: new PublicKey(validator.publicKey),
lamports,
});

const {blockhash} = await connection.getLatestBlockhash();

const messageV0 = new TransactionMessage({
payerKey:keypair.publicKey,
recentBlockhash: blockhash,
instructions : [transferIx],
}).compileToV0Message();

const versionedTx = new VersionedTransaction(messageV0);
versionedTx.sign([keypair]);

console.log(`Sending Payout to validator ${validator.publicKey} with ID ${validatorId} of Amount: ${validator.pendingPayouts}`);

const tx = await connection.sendTransaction(versionedTx);
const payload = {validatorId, tx, payoutAmount: validator.pendingPayouts}
await redis.lpush("txVerification", JSON.stringify(payload));
}
}

} catch (error) {
console.log("Error while sending payouts");
console.error(error);
}


}

async function verifyTxs(){
const payload = await redis.rpop("txVerification");

if(payload){
const {validatorId, tx, payoutAmount} : {validatorId: string, tx: string, payoutAmount: number} = JSON.parse(payload);

// verify the tx
const result = await connection.getParsedTransaction(tx, {maxSupportedTransactionVersion:0, commitment:"confirmed"});

console.log("Transaction Result for ", tx);
console.dir(result, {depth:null});

// The tx may not be updated immmediately
if(!result){
await redis.lpush("txVerification", JSON.stringify({validatorId, tx, payoutAmount}));
return;
}

const preBalances = result?.meta?.preBalances;
const postBalances = result?.meta?.postBalances;

// verify this based on the tx
let isPaidSuccessfully = false;

const fee = result.meta?.fee;

if(preBalances && postBalances && fee && postBalances[1] - preBalances[1] === payoutAmount && preBalances[0] - postBalances[0] === (fee + payoutAmount)){
isPaidSuccessfully = true;
console.log("Transaction verified successfully for ", tx);
}

if(!isPaidSuccessfully){
return;
}
else{

await prismaClient.$transaction(async tx =>{

const validator = await tx.validator.findUnique({
where:{
id:validatorId,
}
});

// This will not happen , just to satisfy TS
if(!validator){
console.log("Validator ID not found in DB", validator)
return;
}

// When We set to 0, The amount after locked will be lost, so to handle that
const balanceToSet = validator.pendingPayouts - payoutAmount ;

await tx.validator.update({
where: {
id: validatorId
},
data: {
payoutLocked: false,
pendingPayouts: balanceToSet,
}
});

})
}

}


}
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@types/jsonwebtoken": "^9.0.9",
"cors": "^2.8.5",
"db": "*",
"cache":"*",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"jwt": "^0.2.0"
Expand Down
36 changes: 36 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/jsonwebtoken": "^9.0.9",
"cache": "*",
"cors": "^2.8.5",
"db": "*",
"express": "^4.21.2",
Expand Down Expand Up @@ -93,6 +94,18 @@
"typescript": "^5.0.0",
},
},
"packages/cache": {
"name": "cache",
"dependencies": {
"ioredis": "^5.6.0",
},
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
"packages/common": {
"name": "common",
"devDependencies": {
Expand All @@ -105,6 +118,7 @@
"packages/db": {
"name": "db",
"dependencies": {
"@prisma/client": "6.5.0",
"prisma": "^6.5.0",
},
"devDependencies": {
Expand Down Expand Up @@ -289,6 +303,8 @@

"@img/sharp-win32-x64": ["@img/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],

"@ioredis/commands": ["@ioredis/[email protected]", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="],

"@jridgewell/resolve-uri": ["@jridgewell/[email protected]", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],

"@jridgewell/sourcemap-codec": ["@jridgewell/[email protected]", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
Expand Down Expand Up @@ -327,6 +343,8 @@

"@nolyfill/is-core-module": ["@nolyfill/[email protected]", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],

"@prisma/client": ["@prisma/[email protected]", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-M6w1Ql/BeiGoZmhMdAZUXHu5sz5HubyVcKukbLs3l0ELcQb8hTUJxtGEChhv4SVJ0QJlwtLnwOLgIRQhpsm9dw=="],

"@prisma/config": ["@prisma/[email protected]", "", { "dependencies": { "esbuild": ">=0.12 <1", "esbuild-register": "3.6.0" } }, "sha512-sOH/2Go9Zer67DNFLZk6pYOHj+rumSb0VILgltkoxOjYnlLqUpHPAN826vnx8HigqnOCxj9LRhT6U7uLiIIWgw=="],

"@prisma/debug": ["@prisma/[email protected]", "", {}, "sha512-fc/nusYBlJMzDmDepdUtH9aBsJrda2JNErP9AzuHbgUEQY0/9zQYZdNlXmKoIWENtio+qarPNe/+DQtrX5kMcQ=="],
Expand Down Expand Up @@ -649,6 +667,8 @@

"bytes": ["[email protected]", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],

"cache": ["cache@workspace:packages/cache"],

"call-bind": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],

"call-bind-apply-helpers": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
Expand Down Expand Up @@ -683,6 +703,8 @@

"clsx": ["[email protected]", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],

"cluster-key-slot": ["[email protected]", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],

"color": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],

"color-convert": ["[email protected]", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
Expand Down Expand Up @@ -751,6 +773,8 @@

"delayed-stream": ["[email protected]", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],

"denque": ["[email protected]", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],

"depd": ["[email protected]", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],

"dequal": ["[email protected]", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
Expand Down Expand Up @@ -1003,6 +1027,8 @@

"internal-slot": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],

"ioredis": ["[email protected]", "", { "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg=="],

"ip-address": ["[email protected]", "", { "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" } }, "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g=="],

"ipaddr.js": ["[email protected]", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
Expand Down Expand Up @@ -1155,10 +1181,14 @@

"lodash": ["[email protected]", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],

"lodash.defaults": ["[email protected]", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],

"lodash.get": ["[email protected]", "", {}, "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="],

"lodash.includes": ["[email protected]", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],

"lodash.isarguments": ["[email protected]", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],

"lodash.isboolean": ["[email protected]", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],

"lodash.isinteger": ["[email protected]", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
Expand Down Expand Up @@ -1351,6 +1381,10 @@

"readable-stream": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],

"redis-errors": ["[email protected]", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],

"redis-parser": ["[email protected]", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],

"reflect.getprototypeof": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],

"regenerator-runtime": ["[email protected]", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="],
Expand Down Expand Up @@ -1449,6 +1483,8 @@

"stable-hash": ["[email protected]", "", {}, "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g=="],

"standard-as-callback": ["[email protected]", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],

"statuses": ["[email protected]", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],

"std-env": ["[email protected]", "", {}, "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA=="],
Expand Down
34 changes: 34 additions & 0 deletions packages/cache/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules

# output
out
dist
*.tgz

# code coverage
coverage
*.lcov

# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# caches
.eslintcache
.cache
*.tsbuildinfo

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store
15 changes: 15 additions & 0 deletions packages/cache/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# cache

To install dependencies:

```bash
bun install
```

To run:

```bash
bun run index.ts
```

This project was created using `bun init` in bun v1.2.4. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
1 change: 1 addition & 0 deletions packages/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Hello via Bun!");
Loading