Skip to content

Commit 431febf

Browse files
authored
Merge pull request #327 from cardanoapi/fix/drep-decode
fix: Update query for drep related endpoints to support scriptDrep
2 parents c3e3cbf + 3eedc18 commit 431febf

File tree

5 files changed

+617
-588
lines changed

5 files changed

+617
-588
lines changed

dbsync-api/src/controllers/drep.ts

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Request, Response, Router } from 'express'
2-
import { handlerWrapper } from '../errors/AppError'
3-
import { convertToHexIfBech32, decodeDrep, isHexValue, validateAddress } from '../helpers/validator'
1+
import {Request, Response, Router} from 'express'
2+
import {handlerWrapper} from '../errors/AppError'
3+
import {convertToHexIfBech32, decodeDrep, isHexValue, validateAddress} from '../helpers/validator'
44
import {
55
fetchDrepDelegationDetails,
66
fetchDrepDetails,
@@ -11,17 +11,13 @@ import {
1111
fetchDrepActiveDelegators,
1212
fetchDrepDelegationHistory,
1313
} from '../repository/drep'
14-
import { DrepSortType, DrepStatusType } from '../types/drep'
14+
import {DrepSortType, DrepStatusType} from '../types/drep'
1515

1616
const router = Router()
1717

1818
const getDrepDetails = async (req: Request, res: Response): Promise<any> => {
19-
20-
let drep = decodeDrep(req.params.id as string)
21-
if (!drep) {
22-
return res.status(400).json({ message: 'Provide a valid Drep ID' })
23-
}
24-
const result = await fetchDrepDetails(drep.credential,drep.isScript)
19+
const drep = decodeDrep(req.params.id as string)
20+
const result = await fetchDrepDetails(drep.credential, drep.isScript)
2521
return res.status(200).json(result)
2622
}
2723

@@ -30,53 +26,41 @@ const getDrepList = async (req: Request, res: Response) => {
3026
const page = req.query.page ? +req.query.page : 1
3127
const status = req.query.status ? (req.query.status as DrepStatusType) : undefined
3228
const sort = req.query.sort ? (req.query.sort as DrepSortType) : undefined
33-
const search = convertToHexIfBech32(req.query.search as string)
34-
const { items, totalCount } = await fetchDrepList(page, size, search, status, sort)
35-
return res.status(200).json({ total: totalCount, page, size, items })
29+
const searchDrep = req.query.search ? decodeDrep(req.query.search as string) : {credential: '', isScript: undefined}
30+
const {
31+
items,
32+
totalCount
33+
} = await fetchDrepList(page, size, searchDrep.credential, searchDrep.isScript, status, sort)
34+
return res.status(200).json({total: totalCount, page, size, items})
3635
}
3736

3837
const getDrepVoteDetails = async (req: Request, res: Response) => {
39-
const dRepId = convertToHexIfBech32(req.params.id as string)
40-
if (dRepId && !isHexValue(dRepId)) {
41-
return res.status(400).json({ message: 'Provide a valid Drep ID' })
42-
}
43-
const result = await fetchDrepVoteDetails(dRepId)
38+
const dRepId = decodeDrep(req.params.id as string)
39+
const result = await fetchDrepVoteDetails(dRepId.credential, dRepId.isScript)
4440
return res.status(200).json(result)
4541
}
4642

4743
const getDrepDelegationDetails = async (req: Request, res: Response) => {
48-
const dRepId = convertToHexIfBech32(req.params.id as string)
49-
if (dRepId && !isHexValue(dRepId)) {
50-
return res.status(400).json({ message: 'Provide a valid Drep ID' })
51-
}
52-
const result = await fetchDrepDelegationHistory(dRepId)
44+
const dRepId = decodeDrep(req.params.id as string)
45+
const result = await fetchDrepDelegationHistory(dRepId.credential, dRepId.isScript)
5346
return res.status(200).json(result)
5447
}
5548

5649
const getDrepRegistrationDetails = async (req: Request, res: Response) => {
57-
const dRepId = convertToHexIfBech32(req.params.id as string)
58-
if (dRepId && !isHexValue(dRepId)) {
59-
return res.status(400).json({ message: 'Provide a valid Drep ID' })
60-
}
61-
const result = await fetchDrepRegistrationDetails(dRepId)
50+
const dRepId = decodeDrep(req.params.id as string)
51+
const result = await fetchDrepRegistrationDetails(dRepId.credential, dRepId.isScript)
6252
return res.status(200).json(result)
6353
}
6454

6555
const getDrepActiveDelegation = async (req: Request, res: Response) => {
66-
const dRepId = convertToHexIfBech32(req.params.id as string)
67-
if (dRepId && !isHexValue(dRepId)) {
68-
return res.status(400).json({ message: 'Provide a valid Drep ID' })
69-
}
70-
const result = await fetchDrepActiveDelegation(dRepId)
56+
const dRepId = decodeDrep(req.params.id as string)
57+
const result = await fetchDrepActiveDelegation(dRepId.credential, dRepId.isScript)
7158
return res.status(200).json(result)
7259
}
7360

7461
const getDrepActiveDelegators = async (req: Request, res: Response) => {
75-
const dRepId = convertToHexIfBech32(req.params.id as string)
76-
if (dRepId && !isHexValue(dRepId)) {
77-
return res.status(400).json({ message: 'Provide a valid Drep ID' })
78-
}
79-
const activeDelegators = await fetchDrepActiveDelegators(dRepId)
62+
const dRepId = decodeDrep(req.params.id as string)
63+
const activeDelegators = await fetchDrepActiveDelegators(dRepId.credential, dRepId.isScript)
8064
return res.status(200).json(activeDelegators)
8165
}
8266

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,37 @@
1-
export function formatResult(result:Array<Record<string,any>>){
2-
return result.map((item)=>item.result)
1+
import {encodeDrep} from "./validator";
2+
3+
export function formatResult(result: Array<Record<string, any>>) {
4+
return result.map((item) => {
5+
const {has_script, drepId} = item.result
6+
return {...item.result, view: encodeDrep(drepId, has_script)}
7+
})
38
}
49

510
export function combineArraysWithSameObjectKey(array1: any, array2: any) {
6-
// Create a map for array2 based on the stake_address
7-
const map2 = new Map();
8-
array2.forEach((item: any) => {
9-
const { stakeAddress, ...rest } = item.json_build_object;
10-
map2.set(stakeAddress, rest); // Use stake_address as the key
11-
});
11+
// Create a map for array2 based on the stake_address
12+
const map2 = new Map();
13+
array2.forEach((item: any) => {
14+
const {stakeAddress, ...rest} = item.json_build_object;
15+
map2.set(stakeAddress, rest); // Use stake_address as the key
16+
});
1217

13-
// Combine the data from array1 and array2 based on stake_address
14-
return array1.map((item1: any) => {
15-
const { stakeAddress, ...rest1 } = item1.json_build_object;
16-
const matchedData = map2.get(stakeAddress);
18+
// Combine the data from array1 and array2 based on stake_address
19+
return array1.map((item1: any) => {
20+
const {stakeAddress, ...rest1} = item1.json_build_object;
21+
const matchedData = map2.get(stakeAddress);
1722

18-
if (matchedData) {
19-
// Combine both objects if match is found
20-
return {
21-
json_build_object: {
22-
...rest1,
23-
...matchedData,
24-
stakeAddress
23+
if (matchedData) {
24+
// Combine both objects if match is found
25+
return {
26+
json_build_object: {
27+
...rest1,
28+
...matchedData,
29+
stakeAddress
30+
}
31+
};
32+
} else {
33+
// Return the original object if no match is found in array2
34+
return item1;
2535
}
26-
};
27-
} else {
28-
// Return the original object if no match is found in array2
29-
return item1;
30-
}
31-
});
36+
});
3237
}
Lines changed: 87 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,114 @@
1-
import { bech32 } from "bech32";
1+
import {bech32} from "bech32";
2+
import AppError from "../errors/AppError";
23

34
export function convertToHexIfBech32(address: string): string {
4-
if (!address) return ''
5-
if (address.startsWith('stake')|| address.startsWith('drep')){
6-
const decoded = bech32.decode(address);
7-
const data = bech32.fromWords(decoded.words);
8-
return Buffer.from(data).toString('hex');
9-
}
10-
else if (address.length === 58 && (address.startsWith('22') || address.startsWith('23'))){
11-
return address.slice(2)
12-
}
13-
return address
5+
if (!address) return ''
6+
if (address.startsWith('stake') || address.startsWith('drep')) {
7+
const decoded = bech32.decode(address);
8+
const data = bech32.fromWords(decoded.words);
9+
return Buffer.from(data).toString('hex');
10+
} else if (address.length === 58 && (address.startsWith('22') || address.startsWith('23'))) {
11+
return address.slice(2)
12+
}
13+
return address
1414
}
1515

16-
export function decodeDrep(address:string):{credential:string,isScript?:boolean}{
17-
const drep = decodeAddress(address);
18-
if(drep.bech32Prefix === 'drep_script'){
19-
return {
20-
credential: drep.credential.toString('hex'),
21-
isScript: true
22-
}
23-
}else{
24-
if(drep.dataHeader.length >1){
25-
throw new Error("Drep credential contains invalid header: "+address)
26-
}
27-
let isScript
28-
const header=drep.dataHeader.at(0)
29-
if(header == 0x22){
30-
isScript = false
31-
}else if (header == 0x23){
32-
isScript = true
33-
}else if ( header){
34-
throw new Error("Drep credential contains invalid header 0x"+ header.toString(16)+ ": "+address)
16+
export function decodeDrep(address: string): { credential: string, isScript?: boolean } {
17+
const drep = decodeAddress(address);
18+
if (drep.bech32Prefix === 'drep_script') {
19+
return {
20+
credential: drep.credential.toString('hex'),
21+
isScript: true
22+
}
23+
} else {
24+
if (drep.dataHeader.length > 1) {
25+
throw new AppError("Drep credential contains invalid header: " + address)
26+
}
27+
let isScript
28+
const header = drep.dataHeader.at(0)
29+
if (header == 0x22) {
30+
isScript = false
31+
} else if (header == 0x23) {
32+
isScript = true
33+
} else if (header) {
34+
throw new AppError("Drep credential contains invalid header 0x" + header.toString(16) + ": " + address)
35+
}
36+
return {
37+
credential: drep.credential.toString('hex'),
38+
isScript: isScript
39+
}
3540
}
36-
return {
37-
credential: drep.credential.toString('hex'),
38-
isScript: isScript
39-
}
40-
}
41+
}
4142

43+
export function encodeDrep(hexValue: string, isScript: boolean) {
44+
try {
45+
let drepHexVal = hexValue
46+
if (isScript) {
47+
drepHexVal = '23' + drepHexVal
48+
} else {
49+
drepHexVal = '22' + drepHexVal
50+
}
51+
const drepBufferVal = Buffer.from(drepHexVal,'hex')
52+
const bech32Words = bech32.toWords(drepBufferVal)
53+
return bech32.encode(isScript ? 'drep_script' : 'drep', bech32Words, 100)
54+
} catch (e: any) {
55+
throw new AppError(e?.message || 'Error During DrepViewConversion: ', e)
56+
}
4257
}
4358

4459

4560
export function decodeAddress(address: string): { bech32Prefix: string, dataHeader: Buffer, credential: Buffer } {
46-
if (!address) return { bech32Prefix: "", dataHeader: Buffer.alloc(0), credential: Buffer.alloc(0) }; // Return empty if address is falsy
61+
if (!address) return {bech32Prefix: "", dataHeader: Buffer.alloc(0), credential: Buffer.alloc(0)}; // Return empty if address is falsy
4762

48-
if (isHexValue(address)) {
49-
// Handle the case where the address is hex-encoded (you can tweak this based on your needs)
50-
const buffer = Buffer.from(address, 'hex');
51-
const dataHeader = buffer.subarray(0, buffer.length - 28);
52-
const credential = buffer.subarray(buffer.length - 28);
53-
return { bech32Prefix: "hex", dataHeader, credential };
54-
} else {
55-
try {
56-
// Decode the Bech32 address
57-
const decoded = bech32.decode(address);
58-
const data = bech32.fromWords(decoded.words);
59-
const buffer = Buffer.from(data);
63+
if (isHexValue(address)) {
64+
// Handle the case where the address is hex-encoded (you can tweak this based on your needs)
65+
const buffer = Buffer.from(address, 'hex');
66+
const dataHeader = buffer.subarray(0, buffer.length - 28);
67+
const credential = buffer.subarray(buffer.length - 28);
68+
return {bech32Prefix: "hex", dataHeader, credential};
69+
} else {
70+
try {
71+
// Decode the Bech32 address
72+
const decoded = bech32.decode(address);
73+
const data = bech32.fromWords(decoded.words);
74+
const buffer = Buffer.from(data);
6075

61-
// Split the buffer into header and credential (last 28 bytes for credential)
62-
const dataHeader = buffer.subarray(0, buffer.length - 28);
63-
const credential = buffer.subarray(buffer.length - 28);
76+
// Split the buffer into header and credential (last 28 bytes for credential)
77+
const dataHeader = buffer.subarray(0, buffer.length - 28);
78+
const credential = buffer.subarray(buffer.length - 28);
6479

65-
return {
66-
bech32Prefix: decoded.prefix, // Extract prefix from the Bech32 decoding result
67-
dataHeader,
68-
credential
69-
};
80+
return {
81+
bech32Prefix: decoded.prefix, // Extract prefix from the Bech32 decoding result
82+
dataHeader,
83+
credential
84+
};
7085

71-
} catch (e: any) {
72-
throw new Error("Data is not hex or bech32: " + address);
86+
} catch (e: any) {
87+
throw new AppError("Data is not hex or bech32: " + address);
88+
}
7389
}
74-
}
7590
}
7691

7792

78-
export function isHexValue(value:string):boolean{
79-
const hexRegex = /^(?:[0-9a-fA-F]{56}|[0-9a-fA-F]{58})$/;
80-
return hexRegex.test(value);
93+
export function isHexValue(value: string): boolean {
94+
const hexRegex = /^(?:[0-9a-fA-F]{56}|[0-9a-fA-F]{58})$/;
95+
return hexRegex.test(value);
8196
}
8297

83-
export function validateHash(value:string) {
84-
const regex = /^[a-f0-9A-F]{64}$/
85-
if (value.includes('#')){
86-
return regex.test(value.slice(0,-2))
87-
}else return regex.test(value)
98+
export function validateHash(value: string) {
99+
const regex = /^[a-f0-9A-F]{64}$/
100+
if (value.includes('#')) {
101+
return regex.test(value.slice(0, -2))
102+
} else return regex.test(value)
88103
}
89104

90105
export function fromHex(prefix: string, hex: string) {
91-
return bech32.encode(prefix, bech32.toWords(Buffer.from(hex, "hex")));
106+
return bech32.encode(prefix, bech32.toWords(Buffer.from(hex, "hex")));
92107
}
93108

94109
export function validateAddress(value: string): boolean {
95-
if (isHexValue(value)){
96-
return value.length === 56 || value.length === 58
97-
}
98-
return false
110+
if (isHexValue(value)) {
111+
return value.length === 56 || value.length === 58
112+
}
113+
return false
99114
}

dbsync-api/src/index.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,18 @@ app.use('/api/health', healthCheckRoute)
6262
app.use('/api/gov-action', govActionRoute)
6363

6464
setupSwaggerUi(app)
65-
const indexFile = path.resolve('.','./index.html')
66-
// Check if index.html exists
67-
fs.access(indexFile, fs.constants.F_OK, (err) => {
68-
if (!err) {
69-
// If index.html exists, define the catch-all handler
70-
app.get('*', (req: Request, res: Response) => {
71-
res.sendFile(indexFile);
72-
});
73-
} else {
74-
console.error('index.html does not exist.');
75-
}
76-
});
65+
// const indexFile = path.resolve('.','./index.html')
66+
// // Check if index.html exists
67+
// fs.access(indexFile, fs.constants.F_OK, (err) => {
68+
// if (!err) {
69+
// // If index.html exists, define the catch-all handler
70+
// app.get('*', (req: Request, res: Response) => {
71+
// res.sendFile(indexFile);
72+
// });
73+
// } else {
74+
// console.error('index.html does not exist.');
75+
// }
76+
// });
7777
app.use(errorHandler);
7878

7979

0 commit comments

Comments
 (0)