Skip to content

Commit 8fdaf93

Browse files
committed
feat(klesia-sdk): better types for results and errors
1 parent a53866f commit 8fdaf93

File tree

14 files changed

+228
-59
lines changed

14 files changed

+228
-59
lines changed

apps/docs/src/pages/klesia/rpc.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ https://mainnet.klesia.palladians.xyz/api
1818
https://devnet.klesia.palladians.xyz/api
1919
```
2020

21+
### Zeko Devnet (soon™️)
22+
23+
```
24+
https://zeko-devnet.klesia.palladians.xyz/api
25+
```
26+
2127
## RPC Methods
2228

2329
Below you can find the complete list of RPC methods available on Klesia.
@@ -50,6 +56,10 @@ Array of strings:
5056

5157
Returns the hash of the most recent block.
5258

59+
:::warning
60+
Not supported on Zeko.
61+
:::
62+
5363
---
5464

5565
### mina_chainId

apps/docs/src/pages/klesia/sdk.mdx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,20 @@ $ npm install @mina-js/klesia-sdk
1212
For now there are only [nightly builds](/klesia/sdk#nightly-builds) available. The stable version will be released soon™️.
1313
:::
1414

15+
## Client Options
16+
17+
- `network`: The network to connect to. One of: `mainnet`, `devnet`, `zeko-devnet`.
18+
- `customUrl`: A custom URL to connect to in case of self-hosted RPCs.
19+
- `throwable`: If `true`, the client will throw an error if the response contains an error. Default is `true`.
20+
1521
## Usage
1622

17-
```typescript
23+
```typescript twoslash
1824
import { createClient } from '@mina-js/klesia-sdk'
1925

2026
const client = createClient({ network: 'devnet' })
2127

22-
const { result } = await client.request({
28+
const { result } = await client.request<'mina_getTransactionCount'>({
2329
method: 'mina_getTransactionCount',
2430
params: ['B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS']
2531
})

apps/klesia/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
MINA_NETWORK=devnet
22
NODE_API_DEVNET=https://api.minascan.io/node/devnet/v1/graphql
33
NODE_API_MAINNET=https://api.minascan.io/node/mainnet/v1/graphql
4+
NODE_API_ZEKO_DEVNET=https://devnet.zeko.io/graphql

apps/klesia/src/index.spec.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ it("returns result for mina_getTransactionCount", async () => {
1111
params: ["B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS"],
1212
},
1313
});
14-
const { result } = await response.json();
15-
expect(result).toBeGreaterThan(0);
14+
const { result } = (await response.json()) as { result: string };
15+
expect(BigInt(result)).toBeGreaterThan(0);
1616
});
1717

1818
it("returns result for mina_getBalance", async () => {
@@ -22,23 +22,23 @@ it("returns result for mina_getBalance", async () => {
2222
params: ["B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS"],
2323
},
2424
});
25-
const { result } = await response.json();
25+
const { result } = (await response.json()) as { result: string };
2626
expect(BigInt(String(result))).toBeGreaterThan(0);
2727
});
2828

2929
it("returns result for mina_blockHash", async () => {
3030
const response = await client.api.$post({
3131
json: { method: "mina_blockHash" },
3232
});
33-
const { result } = await response.json();
34-
expect((result as unknown as string).length).toBeGreaterThan(0);
33+
const { result } = (await response.json()) as { result: string };
34+
expect(result.length).toBeGreaterThan(0);
3535
});
3636

3737
it("returns result for mina_chainId", async () => {
3838
const response = await client.api.$post({
3939
json: { method: "mina_chainId" },
4040
});
41-
const { result } = await response.json();
41+
const { result } = (await response.json()) as { result: string };
4242
expect(result.length).toBeGreaterThan(0);
4343
});
4444

@@ -49,7 +49,8 @@ it("returns result for mina_getAccount", async () => {
4949
params: ["B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS"],
5050
},
5151
});
52-
const { result } = await response.json();
52+
// biome-ignore lint/suspicious/noExplicitAny: TODO
53+
const { result } = (await response.json()) as any;
5354
expect(BigInt(result.nonce)).toBeGreaterThanOrEqual(0);
5455
expect(BigInt(result.balance)).toBeGreaterThanOrEqual(0);
5556
});

apps/klesia/src/index.ts

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { match } from "ts-pattern";
1111
import mainDocs from "../docs/index.txt";
1212
import rpcDocs from "../docs/rpc.txt";
1313
import { mina } from "./methods/mina";
14-
import { RpcMethodSchema, RpcResponseSchema } from "./schema";
14+
import { RpcMethod, RpcMethodSchema, RpcResponseSchema } from "./schema";
1515
import { buildResponse } from "./utils/build-response";
1616

1717
export const api = new OpenAPIHono();
@@ -63,43 +63,92 @@ const rpcRoute = createRoute({
6363
export const klesiaRpcRoute = api.openapi(rpcRoute, async ({ req, json }) => {
6464
const body = req.valid("json");
6565
return match(body)
66-
.with({ method: "mina_getTransactionCount" }, async ({ params }) => {
67-
const [publicKey] = params;
68-
const result = await mina.getTransactionCount({
69-
publicKey: PublicKeySchema.parse(publicKey),
70-
});
71-
return json(buildResponse(result), 200);
72-
})
73-
.with({ method: "mina_getBalance" }, async ({ params }) => {
66+
.with(
67+
{ method: RpcMethod.enum.mina_getTransactionCount },
68+
async ({ params }) => {
69+
const [publicKey] = params;
70+
const result = await mina.getTransactionCount({
71+
publicKey: PublicKeySchema.parse(publicKey),
72+
});
73+
return json(
74+
buildResponse({
75+
method: RpcMethod.enum.mina_getTransactionCount,
76+
result,
77+
}),
78+
200,
79+
);
80+
},
81+
)
82+
.with({ method: RpcMethod.enum.mina_getBalance }, async ({ params }) => {
7483
const [publicKey] = params;
7584
const result = await mina.getBalance({
7685
publicKey: PublicKeySchema.parse(publicKey),
7786
});
78-
return json(buildResponse(result), 200);
87+
return json(
88+
buildResponse({ method: RpcMethod.enum.mina_getBalance, result }),
89+
200,
90+
);
7991
})
80-
.with({ method: "mina_blockHash" }, async () => {
92+
.with({ method: RpcMethod.enum.mina_blockHash }, async () => {
93+
if (process.env.MINA_NETWORK === "zeko_devnet") {
94+
return json(
95+
buildResponse({
96+
method: RpcMethod.enum.mina_blockHash,
97+
error: {
98+
code: -32600,
99+
message: "Network not supported.",
100+
},
101+
}),
102+
200,
103+
);
104+
}
81105
const result = await mina.blockHash();
82-
return json(buildResponse(result), 200);
106+
return json(
107+
buildResponse({ method: RpcMethod.enum.mina_blockHash, result }),
108+
200,
109+
);
83110
})
84-
.with({ method: "mina_chainId" }, async () => {
111+
.with({ method: RpcMethod.enum.mina_chainId }, async () => {
85112
const result = await mina.chainId();
86-
return json(buildResponse(result), 200);
87-
})
88-
.with({ method: "mina_sendTransaction" }, async ({ params }) => {
89-
const [signedTransaction, type] = params;
90-
const result = await mina.sendTransaction({ signedTransaction, type });
91-
return json(buildResponse(result), 200);
113+
return json(
114+
buildResponse({ method: RpcMethod.enum.mina_chainId, result }),
115+
200,
116+
);
92117
})
93-
.with({ method: "mina_getAccount" }, async ({ params }) => {
118+
.with(
119+
{ method: RpcMethod.enum.mina_sendTransaction },
120+
async ({ params }) => {
121+
const [signedTransaction, type] = params;
122+
const result = await mina.sendTransaction({ signedTransaction, type });
123+
return json(
124+
buildResponse({
125+
method: RpcMethod.enum.mina_sendTransaction,
126+
result,
127+
}),
128+
200,
129+
);
130+
},
131+
)
132+
.with({ method: RpcMethod.enum.mina_getAccount }, async ({ params }) => {
94133
const [publicKey] = params;
95134
const result = await mina.getAccount({
96135
publicKey: PublicKeySchema.parse(publicKey),
97136
});
98-
return json(buildResponse(result), 200);
137+
return json(
138+
buildResponse({ method: RpcMethod.enum.mina_getAccount, result }),
139+
200,
140+
);
99141
})
100142
.exhaustive();
101143
});
102144

103145
serve(api);
104146

105147
export type KlesiaRpc = typeof klesiaRpcRoute;
148+
export {
149+
KlesiaNetwork,
150+
RpcMethod,
151+
type RpcMethodType,
152+
type RpcResponseType,
153+
type RpcErrorType,
154+
} from "./schema";

apps/klesia/src/methods/mina.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const TEST_PKEY = "B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS";
55

66
it("should return transactions count", async () => {
77
const result = await mina.getTransactionCount({ publicKey: TEST_PKEY });
8-
expect(result).toBeGreaterThan(0);
8+
expect(BigInt(result)).toBeGreaterThan(0);
99
});
1010

1111
it("should return balance", async () => {

apps/klesia/src/methods/mina.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const getTransactionCount = async ({ publicKey }: { publicKey: string }) => {
1515
`,
1616
{ publicKey },
1717
);
18-
return Number.parseInt(data.account.nonce);
18+
return data.account.nonce;
1919
};
2020

2121
const getBalance = async ({ publicKey }: { publicKey: string }) => {

apps/klesia/src/schema.ts

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,94 @@
11
import { PublicKeySchema } from "@mina-js/shared";
22
import { z } from "zod";
33

4+
export const KlesiaNetwork = z.enum(["devnet", "mainnet", "zeko_devnet"]);
45
export const PublicKeyParamsSchema = z.array(PublicKeySchema).length(1);
56
export const EmptyParamsSchema = z.array(z.string()).length(0).optional();
67
export const SendTransactionSchema = z.array(z.any(), z.string()).length(2);
78

9+
export const RpcMethod = z.enum([
10+
"mina_getTransactionCount",
11+
"mina_getBalance",
12+
"mina_blockHash",
13+
"mina_chainId",
14+
"mina_sendTransaction",
15+
"mina_getAccount",
16+
]);
17+
export type RpcMethodType = z.infer<typeof RpcMethod>;
18+
819
export const RpcMethodSchema = z.discriminatedUnion("method", [
920
z.object({
10-
method: z.literal("mina_getTransactionCount"),
21+
method: z.literal(RpcMethod.enum.mina_getTransactionCount),
1122
params: PublicKeyParamsSchema,
1223
}),
1324
z.object({
14-
method: z.literal("mina_getBalance"),
25+
method: z.literal(RpcMethod.enum.mina_getBalance),
1526
params: PublicKeyParamsSchema,
1627
}),
1728
z.object({
18-
method: z.literal("mina_blockHash"),
29+
method: z.literal(RpcMethod.enum.mina_blockHash),
1930
params: EmptyParamsSchema,
2031
}),
2132
z.object({
22-
method: z.literal("mina_chainId"),
33+
method: z.literal(RpcMethod.enum.mina_chainId),
2334
params: EmptyParamsSchema,
2435
}),
2536
z.object({
26-
method: z.literal("mina_sendTransaction"),
37+
method: z.literal(RpcMethod.enum.mina_sendTransaction),
2738
params: SendTransactionSchema,
2839
}),
2940
z.object({
30-
method: z.literal("mina_getAccount"),
41+
method: z.literal(RpcMethod.enum.mina_getAccount),
3142
params: PublicKeyParamsSchema,
3243
}),
3344
]);
3445

35-
export const RpcResponseSchema = z.object({
46+
export const JsonRpcResponse = z.object({
3647
jsonrpc: z.literal("2.0"),
37-
result: z.any(),
3848
});
49+
50+
export const RpcError = z.object({
51+
code: z.number(),
52+
message: z.string(),
53+
});
54+
55+
export type RpcErrorType = z.infer<typeof RpcError>;
56+
57+
export const ErrorSchema = JsonRpcResponse.extend({
58+
error: RpcError,
59+
});
60+
61+
export const RpcResponseSchema = z.union([
62+
z.discriminatedUnion("method", [
63+
JsonRpcResponse.extend({
64+
method: z.literal(RpcMethod.enum.mina_getTransactionCount),
65+
result: z.string(),
66+
}),
67+
JsonRpcResponse.extend({
68+
method: z.literal(RpcMethod.enum.mina_getBalance),
69+
result: z.string(),
70+
}),
71+
JsonRpcResponse.extend({
72+
method: z.literal(RpcMethod.enum.mina_blockHash),
73+
result: z.string(),
74+
}),
75+
JsonRpcResponse.extend({
76+
method: z.literal(RpcMethod.enum.mina_chainId),
77+
result: z.string(),
78+
}),
79+
JsonRpcResponse.extend({
80+
method: z.literal(RpcMethod.enum.mina_sendTransaction),
81+
result: z.string(),
82+
}),
83+
JsonRpcResponse.extend({
84+
method: z.literal(RpcMethod.enum.mina_getAccount),
85+
result: z.object({
86+
nonce: z.string(),
87+
balance: z.string(),
88+
}),
89+
}),
90+
]),
91+
ErrorSchema,
92+
]);
93+
94+
export type RpcResponseType = z.infer<typeof RpcResponseSchema>;
Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
export const buildResponse = (data: unknown) => ({
2-
jsonrpc: "2.0",
3-
result: data,
4-
});
1+
import type { RpcErrorType, RpcMethodType } from "../schema";
2+
3+
export const buildResponse = ({
4+
result,
5+
error,
6+
method,
7+
}: { result?: unknown; error?: RpcErrorType; method: RpcMethodType }) => {
8+
if (error) {
9+
return {
10+
jsonrpc: "2.0",
11+
error,
12+
method,
13+
};
14+
}
15+
return { jsonrpc: "2.0", result, method };
16+
};

apps/klesia/src/utils/node.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { Client, cacheExchange, fetchExchange } from "@urql/core";
22
import { match } from "ts-pattern";
33
import { z } from "zod";
4+
import { KlesiaNetwork } from "../schema";
45

5-
const NetworkMatcher = z.enum(["devnet", "mainnet"]);
6-
7-
const MINA_NETWORK = NetworkMatcher.parse(process.env.MINA_NETWORK ?? "devnet");
6+
const MINA_NETWORK = KlesiaNetwork.parse(process.env.MINA_NETWORK ?? "devnet");
87
const NODE_API_DEVNET = z
98
.string()
109
.parse(
@@ -17,11 +16,15 @@ const NODE_API_MAINNET = z
1716
process.env.NODE_API_MAINNET ??
1817
"https://api.minascan.io/node/mainnet/v1/graphql",
1918
);
19+
const NODE_API_ZEKO_DEVNET = z
20+
.string()
21+
.parse(process.env.NODE_API_ZEKO_DEVNET ?? "https://devnet.zeko.io/graphql");
2022

2123
export const getNodeApiUrl = () => {
2224
return match(MINA_NETWORK)
2325
.with("devnet", () => NODE_API_DEVNET)
2426
.with("mainnet", () => NODE_API_MAINNET)
27+
.with("zeko_devnet", () => NODE_API_ZEKO_DEVNET)
2528
.exhaustive();
2629
};
2730

0 commit comments

Comments
 (0)