This repository has been archived by the owner on Oct 6, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils.ts
167 lines (151 loc) · 4.87 KB
/
utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import {
isAllegraBlock,
isAlonzoBlock,
isBabbageBlock,
isByronBlock,
isMaryBlock,
isShelleyBlock,
Schema,
} from "@cardano-ogmios/client";
import _, { Dictionary } from "lodash";
import { match, P } from "ts-pattern";
import cbor from "cbor";
import { Block as DbBlock } from "../../db/models";
export type SupportedBlock =
| ({
type: "Babbage";
} & Schema.BlockBabbage)
| ({
type: "Alonzo";
} & Schema.BlockAlonzo)
| ({
type: "Mary";
} & Schema.BlockMary);
export type SupportedTx = Schema.TxAlonzo | Schema.TxBabbage | Schema.TxMary;
export type PlutusSupportedTx = Schema.TxAlonzo | Schema.TxBabbage;
export type Recorder = (block: SupportedBlock, dbBlock: DbBlock) => Promise<void>;
export type Rollback = (point: Schema.PointOrOrigin) => Promise<void>;
const exhaustiveGuard = (_: never): never => {
throw new Error("Exhaustive guard");
};
export function getSupportedBlock(block: Schema.Block): SupportedBlock | null {
if (isByronBlock(block) || isAllegraBlock(block) || isShelleyBlock(block)) {
// tokens were not supported in these eras
return null;
} else if (isBabbageBlock(block)) {
// do a bit of monkeypatching for performance reasons
return Object.assign(block.babbage, { type: "Babbage" as const });
} else if (isAlonzoBlock(block)) {
return Object.assign(block.alonzo, { type: "Alonzo" as const });
} else if (isMaryBlock(block)) {
return Object.assign(block.mary, { type: "Mary" as const });
}
return exhaustiveGuard(block);
}
/**
* Lossy parse of metadatum into a JS format.
* bytes -> base16 string
* int -> bigint
* map -> Record<string, *>
*/
export function parseMetadatumLossy(metadatum: Schema.Metadatum): unknown {
return match(metadatum)
.with({ int: P.select("val") }, ({ val }) => BigInt(val))
.with({ string: P.select("val") }, ({ val }) => val)
.with({ bytes: P.select("val") }, ({ val }) => val /* use the hex encoded bytes */)
.with({ list: P.select("val") }, ({ val }) => val.map(parseMetadatumLossy))
.with({ map: P.select("val") }, ({ val }) => {
const record: Record<string, unknown> = {};
val.forEach(({ k, v }) => {
const objKey = parseMetadatumLossy(k);
// in case it was a split string key, join it
// as default the obj could be stringified.
// this is less strict and won't work with non-string keys
const key = joinStringIfNeeded(objKey) || String(objKey);
record[key] = parseMetadatumLossy(v);
});
return record;
})
.exhaustive();
}
/**
* Expects either a string or an array of strings that was split because of
* the metadatum constraints
*/
export function joinStringIfNeeded(value: unknown): string | null {
if (_.isArray(value) && (value.length === 0 || _.isString(value[0]))) {
return value.join("");
} else if (_.isString(value)) {
return value;
} else {
return null;
}
}
export function safeJSONStringify(obj: unknown): string {
return JSON.stringify(
obj,
(_key, value) => (typeof value === "bigint" ? value.toString() : value) // return everything else unchanged
);
}
export type PlutusDatum =
| string
| number
| bigint
| Buffer
| Map<PlutusDatum, PlutusDatum>
| PlutusDatum[]
| ConstrData;
export class ConstrData {
constructor(public constr: number, public fields: PlutusDatum[]) {}
}
const DatumDecoderOptions = {
encoding: "hex" as const,
tags: Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_.range(121, 128).map((tag) => [tag, (val: any) => new ConstrData(tag - 121, val)])
),
};
export function decodeDatumSync(datum: string): PlutusDatum {
return cbor.decodeFirstSync(datum, DatumDecoderOptions);
}
export type ObjectPlutusDatum =
| string
| number
| bigint
| Buffer
| ObjectPlutusDatum[]
| Dictionary<ObjectPlutusDatum>;
export function parseDatumLossy(
datum: PlutusDatum,
{ bufferEncoding = "hex" }: { bufferEncoding?: BufferEncoding } = {}
): ObjectPlutusDatum {
return match(datum)
.when(_.isString, (v) => v)
.when(_.isNumber, (v) => v)
.when(
(v): v is bigint => typeof v === "bigint",
(v) => v
)
.when(Buffer.isBuffer, (v) => v.toString(bufferEncoding))
.when(
(v): v is Map<PlutusDatum, PlutusDatum> => v instanceof Map,
(v) =>
Object.fromEntries(
Array.from(v).map(([key, val]) => [
parseDatumLossy(key, { bufferEncoding: "utf-8" }),
parseDatumLossy(val, { bufferEncoding }),
])
)
)
.when(_.isArray, (v) => v.map((datum) => parseDatumLossy(datum, { bufferEncoding })))
.when(
(v): v is ConstrData => v instanceof ConstrData,
(v) =>
Object.fromEntries(
v.fields
.map((field, index) => [`_${index}`, parseDatumLossy(field, { bufferEncoding })])
.concat([["constr", v.constr]])
)
)
.exhaustive();
}