Skip to content

Commit 4aa764a

Browse files
authored
Merge pull request #35 from syscoin/admin-panel
Admin panel
2 parents f19d45b + 5e4edf0 commit 4aa764a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2862
-462
lines changed

.github/workflows/aws.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
push:
77
branches:
88
- main
9-
- sponsor-wallet-implementation
9+
- admin-panel
1010

1111
env:
1212
AWS_REGION: us-east-1 # set this to your preferred AWS region, e.g. us-west-1
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { BlockbookAPIURL } from "@contexts/Transfer/constants";
2+
import { utils as syscoinUtils } from "syscoinjs-lib";
3+
4+
export const CONFIRM_UTXO_TRANSACTION = "Confirm UTXO Transaction";
5+
export const BURN_SYS_TOKEN_TYPE = "SPTSyscoinBurnToAssetAllocation";
6+
export const BURN_SYSX_TOKEN_TYPE = "SPTAssetAllocationBurnToNEVM";
7+
8+
export const verifyTxTokenTransfer = async (
9+
txId: string,
10+
tokenType: string
11+
) => {
12+
const rawTransaction = await syscoinUtils.fetchBackendRawTx(
13+
BlockbookAPIURL,
14+
txId
15+
);
16+
17+
return rawTransaction.tokenType === tokenType ? rawTransaction : null;
18+
};
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { AddBurnSysLogRequestPayload } from "api/types/admin/transfer/add-log";
2+
import dbConnect from "lib/mongodb";
3+
import { NextApiRequest, NextApiResponse } from "next";
4+
import TransferModel from "models/transfer";
5+
import { BlockbookAPIURL } from "@contexts/Transfer/constants";
6+
import { utils as syscoinUtils } from "syscoinjs-lib";
7+
import {
8+
ITransferLog,
9+
SYS_TO_ETH_TRANSFER_STATUS,
10+
} from "@contexts/Transfer/types";
11+
import { verifySignature } from "utils/api/verify-signature";
12+
import { CONFIRM_UTXO_TRANSACTION, verifyTxTokenTransfer } from "./constants";
13+
14+
export const handleBurnSys = async (
15+
transferId: string,
16+
payload: AddBurnSysLogRequestPayload,
17+
req: NextApiRequest,
18+
res: NextApiResponse
19+
) => {
20+
await dbConnect();
21+
const { address } = req.session.user!;
22+
const transfer = await TransferModel.findOne({ id: transferId });
23+
if (!transfer) {
24+
return res.status(404).json({ message: "Transfer not found" });
25+
}
26+
const { txId, clearAll, operation, signedMessage } = payload;
27+
const data = {
28+
operation,
29+
txId,
30+
clearAll,
31+
};
32+
const message = JSON.stringify(data);
33+
34+
if (!verifySignature(message, signedMessage, address)) {
35+
return res.status(401).json({ message: "Unauthorized" });
36+
}
37+
38+
const verifiedTransaction = await verifyTxTokenTransfer(
39+
txId,
40+
"SPTSyscoinBurnToAssetAllocation"
41+
);
42+
43+
if (!verifiedTransaction) {
44+
return res.status(400).json({ message: "Invalid transaction type" });
45+
}
46+
47+
if (clearAll) {
48+
transfer.logs = transfer.logs.filter((log) => !(log.status === "burn-sys"));
49+
}
50+
51+
const burnSysLog: ITransferLog = {
52+
status: SYS_TO_ETH_TRANSFER_STATUS.BURN_SYS,
53+
payload: {
54+
data: {
55+
tx: txId,
56+
},
57+
message: "Burning SYS to SYSX",
58+
previousStatus: SYS_TO_ETH_TRANSFER_STATUS.BURN_SYS,
59+
},
60+
date: Date.now(),
61+
};
62+
63+
transfer.logs.push(burnSysLog);
64+
65+
const confirmLog: ITransferLog = {
66+
status: SYS_TO_ETH_TRANSFER_STATUS.BURN_SYS,
67+
payload: {
68+
data: verifiedTransaction,
69+
message: CONFIRM_UTXO_TRANSACTION,
70+
previousStatus: SYS_TO_ETH_TRANSFER_STATUS.BURN_SYS,
71+
},
72+
date: Date.now(),
73+
};
74+
75+
transfer.logs.push(confirmLog);
76+
77+
await transfer.save();
78+
79+
res.status(200).json({ success: true });
80+
};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { AddBurnSysxLogRequestPayload } from "api/types/admin/transfer/add-log";
2+
import dbConnect from "lib/mongodb";
3+
import { NextApiRequest, NextApiResponse } from "next";
4+
import TransferModel from "models/transfer";
5+
import {
6+
ITransferLog,
7+
SYS_TO_ETH_TRANSFER_STATUS,
8+
} from "@contexts/Transfer/types";
9+
import { verifySignature } from "utils/api/verify-signature";
10+
import {
11+
BURN_SYSX_TOKEN_TYPE,
12+
CONFIRM_UTXO_TRANSACTION,
13+
verifyTxTokenTransfer,
14+
} from "./constants";
15+
16+
export const handleBurnSysx = async (
17+
transferId: string,
18+
payload: AddBurnSysxLogRequestPayload,
19+
req: NextApiRequest,
20+
res: NextApiResponse
21+
) => {
22+
await dbConnect();
23+
const { address } = req.session.user!;
24+
const transfer = await TransferModel.findOne({ id: transferId });
25+
if (!transfer) {
26+
return res.status(404).json({ message: "Transfer not found" });
27+
}
28+
const { txId, clearAll, operation, signedMessage } = payload;
29+
const data = {
30+
operation,
31+
txId,
32+
clearAll,
33+
};
34+
const message = JSON.stringify(data);
35+
36+
if (!verifySignature(message, signedMessage, address)) {
37+
return res.status(401).json({ message: "Unauthorized" });
38+
}
39+
40+
const verifiedTransaction = await verifyTxTokenTransfer(
41+
txId,
42+
BURN_SYSX_TOKEN_TYPE
43+
);
44+
45+
if (!verifiedTransaction) {
46+
return res.status(400).json({ message: "Invalid transaction type" });
47+
}
48+
49+
if (clearAll) {
50+
transfer.logs = transfer.logs.filter(
51+
(log) => !(log.status === SYS_TO_ETH_TRANSFER_STATUS.BURN_SYSX)
52+
);
53+
}
54+
55+
const burnSysxLog: ITransferLog = {
56+
status: SYS_TO_ETH_TRANSFER_STATUS.BURN_SYSX,
57+
payload: {
58+
data: {
59+
tx: txId,
60+
},
61+
message: "Burning SYSX to NEVM",
62+
previousStatus: SYS_TO_ETH_TRANSFER_STATUS.BURN_SYS,
63+
},
64+
date: Date.now(),
65+
};
66+
67+
transfer.logs.push(burnSysxLog);
68+
69+
const confirmLog: ITransferLog = {
70+
status: SYS_TO_ETH_TRANSFER_STATUS.BURN_SYSX,
71+
payload: {
72+
data: verifiedTransaction,
73+
message: CONFIRM_UTXO_TRANSACTION,
74+
previousStatus: SYS_TO_ETH_TRANSFER_STATUS.BURN_SYS,
75+
},
76+
date: Date.now(),
77+
};
78+
79+
transfer.logs.push(confirmLog);
80+
81+
await transfer.save();
82+
83+
res.status(200).json({ success: true });
84+
};
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { AddSubmitProofsLogRequestPayload } from "api/types/admin/transfer/add-log";
2+
import dbConnect from "lib/mongodb";
3+
import { NextApiRequest, NextApiResponse } from "next";
4+
import { verifySignature } from "utils/api/verify-signature";
5+
import TransferModel from "models/transfer";
6+
import Web3 from "web3";
7+
import relayAbi from "@contexts/Transfer/relay-abi";
8+
import { RELAY_CONTRACT_ADDRESS } from "@constants";
9+
import {
10+
COMMON_STATUS,
11+
ITransferLog,
12+
SYS_TO_ETH_TRANSFER_STATUS,
13+
} from "@contexts/Transfer/types";
14+
15+
export const handleSubmitProofs = async (
16+
transferId: string,
17+
payload: AddSubmitProofsLogRequestPayload,
18+
req: NextApiRequest,
19+
res: NextApiResponse
20+
) => {
21+
await dbConnect();
22+
const web3 = new Web3("https://rpc.syscoin.org");
23+
24+
const { address } = req.session.user!;
25+
26+
const transfer = await TransferModel.findOne({ id: transferId });
27+
if (!transfer) {
28+
return res.status(404).json({ message: "Transfer not found" });
29+
}
30+
31+
const { clearAll, signedMessage, txHash, operation } = payload;
32+
33+
const data = {
34+
operation,
35+
txHash,
36+
clearAll,
37+
};
38+
const message = JSON.stringify(data);
39+
if (!verifySignature(message, signedMessage, address)) {
40+
return res.status(401).json({ message: "Unauthorized" });
41+
}
42+
43+
const receipt = await web3.eth.getTransactionReceipt(txHash);
44+
45+
if (!receipt) {
46+
return res.status(400).json({
47+
message: "Invalid transaction hash: Transaction not found",
48+
});
49+
}
50+
51+
if (
52+
!receipt.to ||
53+
receipt.to.toLowerCase() !== RELAY_CONTRACT_ADDRESS.toLowerCase()
54+
) {
55+
return res.status(400).json({
56+
message: "Invalid transaction: To is not the relay contract address",
57+
});
58+
}
59+
60+
if (receipt.logs.length === 0) {
61+
return res.status(400).json({
62+
message: "Invalid transaction: No logs found",
63+
});
64+
}
65+
66+
if (clearAll) {
67+
transfer.logs = transfer.logs.filter(
68+
(log) =>
69+
!(
70+
log.status === SYS_TO_ETH_TRANSFER_STATUS.SUBMIT_PROOFS ||
71+
log.status === COMMON_STATUS.FINALIZING
72+
)
73+
);
74+
}
75+
76+
const burnSysxLog: ITransferLog = {
77+
payload: {
78+
data: {
79+
hash: receipt.transactionHash,
80+
},
81+
message: "submit-proofs",
82+
previousStatus: SYS_TO_ETH_TRANSFER_STATUS.GENERATE_PROOFS,
83+
},
84+
status: SYS_TO_ETH_TRANSFER_STATUS.SUBMIT_PROOFS,
85+
date: Date.now(),
86+
};
87+
88+
transfer.logs.push(burnSysxLog);
89+
90+
const confirmLog: ITransferLog = {
91+
status: COMMON_STATUS.FINALIZING,
92+
payload: {
93+
data: receipt,
94+
message: "Confirm NEVM Transaction",
95+
previousStatus: SYS_TO_ETH_TRANSFER_STATUS.SUBMIT_PROOFS,
96+
},
97+
date: Date.now(),
98+
};
99+
100+
transfer.logs.push(confirmLog);
101+
102+
await transfer.save();
103+
104+
res.status(200).json({ success: true });
105+
};

api/services/admin.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Admin, { IAdmin } from "../../models/admin";
2+
export class AdminService {
3+
public async isAdmin(address: string): Promise<boolean> {
4+
const admin = await Admin.exists({ address }).exec();
5+
6+
return admin !== null;
7+
}
8+
9+
public async getAdmin(address: string): Promise<IAdmin | null> {
10+
const admin = await Admin.findOne({ address }).exec();
11+
if (!admin) {
12+
return null;
13+
}
14+
15+
return admin;
16+
}
17+
18+
public async createAdmin(address: string, name: string): Promise<IAdmin> {
19+
// Check if address is already an admin
20+
const isAdmin = await this.isAdmin(address);
21+
22+
if (isAdmin) {
23+
throw new Error("Address is already an admin");
24+
}
25+
26+
const admin = new Admin({ address, name });
27+
28+
return await admin.save();
29+
}
30+
}

api/types/admin.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ITransfer } from "@contexts/Transfer/types";
2+
3+
export type Change = {
4+
property: keyof ITransfer;
5+
from: any;
6+
to: any;
7+
};
8+
9+
export interface OverrideTransferRequestBody {
10+
changes: Change[];
11+
signedMessage: string;
12+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
type BaseLog = {
2+
clearAll: boolean;
3+
signedMessage: string;
4+
};
5+
6+
type BaseUtxoTransaction = {
7+
txId: string;
8+
} & BaseLog;
9+
10+
type BaseEVMTransaction = {
11+
txHash: string;
12+
} & BaseLog;
13+
14+
export type AddBurnSysLogRequestPayload = {
15+
operation: "burn-sys";
16+
} & BaseUtxoTransaction;
17+
18+
export type AddBurnSysxLogRequestPayload = {
19+
operation: "burn-sysx";
20+
} & BaseUtxoTransaction;
21+
22+
export type AddSubmitProofsLogRequestPayload = {
23+
operation: "submit-proofs";
24+
} & BaseEVMTransaction;
25+
26+
export type AddUTXOLogRequestPayload =
27+
| AddBurnSysLogRequestPayload
28+
| AddBurnSysxLogRequestPayload;
29+
30+
export type AddNEVMLogRequestPayload = AddSubmitProofsLogRequestPayload;
31+
32+
export type AddLogRequestPayload =
33+
| AddUTXOLogRequestPayload
34+
| AddNEVMLogRequestPayload;

0 commit comments

Comments
 (0)