Skip to content

Commit 479014c

Browse files
committed
Fix Kupmios quantity decoding
1 parent 027eacd commit 479014c

13 files changed

Lines changed: 2964 additions & 1227 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@lucid-evolution/lucid": patch
3+
"@lucid-evolution/provider": patch
4+
---
5+
6+
Fix Kupmios Kupo quantity decoding and harden Ogmios response handling.

docs/next-env.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3+
/// <reference path="./.next/types/routes.d.ts" />
34

45
// NOTE: This file should not be edited
5-
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
6+
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

docs/tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

package.json

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,73 @@
2323
"engines": {
2424
"node": ">=18"
2525
},
26-
"packageManager": "pnpm@9.12.3"
26+
"packageManager": "pnpm@9.12.3",
27+
"pnpm": {
28+
"overrides": {
29+
"esbuild@<=0.24.2": ">=0.25.0",
30+
"estree-util-value-to-estree@<3.3.3": ">=3.3.3",
31+
"@babel/runtime@<7.26.10": ">=7.26.10",
32+
"cross-spawn@<6.0.6": ">=6.0.6",
33+
"brace-expansion@>=1.0.0 <=1.1.11": ">=1.1.12",
34+
"brace-expansion@>=2.0.0 <=2.0.1": ">=2.0.2",
35+
"next@>=13.0 <14.2.30": ">=14.2.30",
36+
"dompurify@<3.2.4": ">=3.2.4",
37+
"@eslint/plugin-kit@<0.3.4": ">=0.3.4",
38+
"next@>=0.9.9 <14.2.31": ">=14.2.31",
39+
"mermaid@>=10.9.0-rc.1 <10.9.4": ">=10.9.4",
40+
"vite@<=5.4.19": "5.4.21",
41+
"vite@>=6.0.0 <=6.3.5": "6.4.2",
42+
"next@>=0.9.9 <14.2.32": ">=14.2.32",
43+
"vite@>=5.2.6 <=5.4.20": "5.4.21",
44+
"vite@>=6.0.0 <=6.4.0": "6.4.2",
45+
"tar-fs@>=2.0.0 <2.1.4": ">=2.1.4",
46+
"sha.js@<=2.4.11": ">=2.4.12",
47+
"tmp@<=0.2.3": ">=0.2.4",
48+
"glob@>=10.2.0 <10.5.0": ">=10.5.0",
49+
"next@>=13.3.0 <14.2.34": ">=14.2.34",
50+
"next@>=13.3.1-canary.0 <14.2.35": ">=14.2.35",
51+
"lodash-es@>=4.0.0 <=4.17.22": ">=4.17.23",
52+
"lodash@>=4.0.0 <=4.17.22": ">=4.17.23",
53+
"undici@>=7.0.0 <7.18.2": ">=7.18.2",
54+
"next@>=10.0.0 <15.5.10": ">=15.5.10",
55+
"next@>=13.0.0 <15.0.8": ">=15.0.8",
56+
"diff@>=5.0.0 <5.2.2": ">=5.2.2",
57+
"js-yaml@<3.14.2": "3.14.2",
58+
"js-yaml@>=4.0.0 <4.1.1": ">=4.1.1",
59+
"mdast-util-to-hast@>=13.0.0 <13.2.1": ">=13.2.1",
60+
"next-mdx-remote@>=4.3.0 <6.0.0": ">=6.0.0",
61+
"minimatch@<3.1.3": ">=3.1.3",
62+
"minimatch@>=9.0.0 <9.0.6": ">=9.0.6",
63+
"rollup@>=4.0.0 <4.59.0": ">=4.59.0",
64+
"minimatch@>=9.0.0 <9.0.7": ">=9.0.7",
65+
"minimatch@<3.1.4": ">=3.1.4",
66+
"ajv@<6.14.0": ">=6.14.0",
67+
"ajv@>=7.0.0-alpha.0 <8.18.0": ">=8.18.0",
68+
"dompurify@>=3.1.3 <3.2.7": ">=3.2.7",
69+
"flatted@<3.4.0": ">=3.4.0",
70+
"undici@>=7.0.0 <7.24.0": ">=7.24.0",
71+
"next@>=9.5.0 <15.5.13": ">=15.5.13",
72+
"next@>=10.0.0 <15.5.14": ">=15.5.14",
73+
"effect@<3.20.0": ">=3.20.0",
74+
"flatted@<=3.4.1": ">=3.4.2",
75+
"dompurify@<3.3.2": ">=3.3.2",
76+
"brace-expansion@<1.1.13": ">=1.1.13",
77+
"brace-expansion@>=2.0.0 <2.0.3": ">=2.0.3",
78+
"picomatch@<2.3.2": ">=2.3.2",
79+
"picomatch@>=4.0.0 <4.0.4": ">=4.0.4",
80+
"yaml@>=2.0.0 <2.8.3": ">=2.8.3",
81+
"dompurify@>=3.1.3 <=3.3.1": ">=3.3.2",
82+
"lodash.template@>=4.0.0 <4.18.0": ">=4.18.0",
83+
"lodash-es@>=4.0.0 <=4.17.23": ">=4.18.0",
84+
"lodash@>=4.0.0 <=4.17.23": ">=4.18.0",
85+
"lodash-es@<=4.17.23": ">=4.18.0",
86+
"lodash@<=4.17.23": ">=4.18.0",
87+
"dompurify@<=3.3.1": ">=3.3.2",
88+
"vite@<=6.4.1": "6.4.2",
89+
"vite@>=6.0.0 <=6.4.1": "6.4.2",
90+
"next@>=13.0.0 <15.5.15": ">=15.5.15",
91+
"dompurify@<=3.3.3": ">=3.4.0",
92+
"protobufjs@<7.5.5": ">=7.5.5"
93+
}
94+
}
2795
}

packages/lucid/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"tsup": "^8.3.6",
7575
"tsx": "^4.19.2",
7676
"typescript": "5.5.0-dev.20240430",
77-
"vite-plugin-wasm": "^3.4.1",
77+
"vite-plugin-wasm": "^3.6.0",
7878
"vitest": "^2.1.8"
7979
}
8080
}

packages/provider/src/internal/kupo.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { Schema as S } from "effect";
22

3+
const QuantitySchema = S.Union(
4+
S.Number.pipe(S.filter(Number.isSafeInteger)),
5+
S.String.pipe(S.pattern(/^\d+$/)),
6+
);
7+
38
export const ValueSchema = S.Struct({
4-
coins: S.Number,
5-
assets: S.Record({ key: S.String, value: S.Number }),
9+
coins: QuantitySchema,
10+
assets: S.Record({ key: S.String, value: QuantitySchema }),
611
});
712
export interface Value extends S.Schema.Type<typeof ValueSchema> {}
813

packages/provider/src/internal/ogmios.ts

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,50 @@ export const JSONRPCSchema = <A, I, R>(schema: S.Schema<A, I, R>) =>
1111
result: schema,
1212
}).annotations({ identifier: "JSONRPCSchema" });
1313

14+
export const JSONRPCErrorSchema = S.Struct({
15+
code: S.Number,
16+
message: S.String,
17+
data: S.optional(S.Unknown),
18+
});
19+
export type JSONRPCError = S.Schema.Type<typeof JSONRPCErrorSchema>;
20+
21+
const JSONRPCErrorResponseSchema = S.Struct({
22+
jsonrpc: S.String,
23+
method: S.optional(S.String),
24+
id: S.NullOr(S.Number),
25+
error: JSONRPCErrorSchema,
26+
});
27+
export type JSONRPCErrorResponse = S.Schema.Type<
28+
typeof JSONRPCErrorResponseSchema
29+
>;
30+
31+
export type JSONRPCResponse<A> =
32+
| {
33+
jsonrpc: string;
34+
method?: string;
35+
id: number | null;
36+
result: A;
37+
}
38+
| JSONRPCErrorResponse;
39+
40+
export const JSONRPCResponseSchema = <A, I, R>(schema: S.Schema<A, I, R>) =>
41+
S.Union(JSONRPCSchema(schema), JSONRPCErrorResponseSchema).annotations({
42+
identifier: "JSONRPCResponseSchema",
43+
});
44+
45+
export const getJSONRPCResult = <A>(response: JSONRPCResponse<A>): A => {
46+
if ("error" in response) {
47+
const data =
48+
response.error.data === undefined
49+
? ""
50+
: `: ${JSON.stringify(response.error.data)}`;
51+
throw new Error(
52+
`Ogmios JSON-RPC error ${response.error.code}: ${response.error.message}${data}`,
53+
);
54+
}
55+
return response.result;
56+
};
57+
1458
const LovelaceAsset = S.Struct({
1559
lovelace: S.Number,
1660
});
@@ -104,16 +148,33 @@ export const ProtocolParametersSchema = S.Struct({
104148
export interface ProtocolParameters
105149
extends S.Schema.Type<typeof ProtocolParametersSchema> {}
106150

107-
export const Delegation = S.NullOr(
151+
const RewardAccountAdaSchema = S.Struct({
152+
ada: LovelaceAsset,
153+
});
154+
155+
const LegacyDelegationSummarySchema = S.Struct({
156+
delegate: S.optional(S.NullOr(S.Struct({ id: S.String }))),
157+
rewards: RewardAccountAdaSchema,
158+
deposit: RewardAccountAdaSchema,
159+
});
160+
161+
const RewardAccountSummarySchema = S.Struct({
162+
from: S.optional(S.Literal("key", "script")),
163+
credential: S.optional(S.String),
164+
stakePool: S.optional(S.NullOr(S.Struct({ id: S.String }))),
165+
rewards: RewardAccountAdaSchema,
166+
deposit: RewardAccountAdaSchema,
167+
});
168+
169+
export const Delegation = S.Union(
170+
S.Null,
108171
S.Record({
109172
key: S.String,
110-
value: S.Struct({
111-
delegate: S.Struct({ id: S.String }),
112-
rewards: S.Struct({ ada: S.Struct({ lovelace: S.Number }) }),
113-
deposit: S.Struct({ ada: S.Struct({ lovelace: S.Number }) }),
114-
}),
173+
value: LegacyDelegationSummarySchema,
115174
}),
175+
S.Array(RewardAccountSummarySchema),
116176
);
177+
export type Delegation = S.Schema.Type<typeof Delegation>;
117178

118179
type Script = {
119180
language: "plutus:v1" | "plutus:v2" | "plutus:v3";
@@ -136,6 +197,16 @@ export type UTxO = {
136197
script?: Script | null;
137198
};
138199

200+
const toOgmiosQuantity = (amount: bigint): number => {
201+
const quantity = Number(amount);
202+
if (amount < 0n || !Number.isSafeInteger(quantity)) {
203+
throw new Error(
204+
`Cannot encode Ogmios quantity ${amount.toString()}. Ogmios expects JSON numbers, so quantities must be non-negative safe integers.`,
205+
);
206+
}
207+
return quantity;
208+
};
209+
139210
export const RedeemerSchema = S.Struct({
140211
validator: S.Struct({
141212
purpose: S.Literal(
@@ -193,7 +264,8 @@ export const toOgmiosUTxOs = (utxos: CoreType.UTxO[] | undefined): UTxO[] => {
193264
if (!newAssets[policyId]) {
194265
newAssets[policyId] = {};
195266
}
196-
return (newAssets[policyId][assetName ? assetName : ""] = Number(amount));
267+
return (newAssets[policyId][assetName ? assetName : ""] =
268+
toOgmiosQuantity(amount));
197269
});
198270
return newAssets;
199271
};
@@ -206,7 +278,7 @@ export const toOgmiosUTxOs = (utxos: CoreType.UTxO[] | undefined): UTxO[] => {
206278
index: utxo.outputIndex,
207279
address: utxo.address,
208280
value: {
209-
ada: { lovelace: Number(utxo.assets["lovelace"]) },
281+
ada: { lovelace: toOgmiosQuantity(utxo.assets["lovelace"]) },
210282
...toOgmiosAssets(utxo.assets),
211283
},
212284
datumHash: utxo.datumHash,

0 commit comments

Comments
 (0)