Skip to content

Commit

Permalink
feat(klesia-sdk): better types for results and errors
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcnk committed Sep 8, 2024
1 parent a53866f commit 8fdaf93
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 59 deletions.
10 changes: 10 additions & 0 deletions apps/docs/src/pages/klesia/rpc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ https://mainnet.klesia.palladians.xyz/api
https://devnet.klesia.palladians.xyz/api
```

### Zeko Devnet (soon™️)

```
https://zeko-devnet.klesia.palladians.xyz/api
```

## RPC Methods

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

Returns the hash of the most recent block.

:::warning
Not supported on Zeko.
:::

---

### mina_chainId
Expand Down
10 changes: 8 additions & 2 deletions apps/docs/src/pages/klesia/sdk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ $ npm install @mina-js/klesia-sdk
For now there are only [nightly builds](/klesia/sdk#nightly-builds) available. The stable version will be released soon™️.
:::

## Client Options

- `network`: The network to connect to. One of: `mainnet`, `devnet`, `zeko-devnet`.
- `customUrl`: A custom URL to connect to in case of self-hosted RPCs.
- `throwable`: If `true`, the client will throw an error if the response contains an error. Default is `true`.

## Usage

```typescript
```typescript twoslash
import { createClient } from '@mina-js/klesia-sdk'

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

const { result } = await client.request({
const { result } = await client.request<'mina_getTransactionCount'>({
method: 'mina_getTransactionCount',
params: ['B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS']
})
Expand Down
1 change: 1 addition & 0 deletions apps/klesia/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
MINA_NETWORK=devnet
NODE_API_DEVNET=https://api.minascan.io/node/devnet/v1/graphql
NODE_API_MAINNET=https://api.minascan.io/node/mainnet/v1/graphql
NODE_API_ZEKO_DEVNET=https://devnet.zeko.io/graphql
15 changes: 8 additions & 7 deletions apps/klesia/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ it("returns result for mina_getTransactionCount", async () => {
params: ["B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS"],
},
});
const { result } = await response.json();
expect(result).toBeGreaterThan(0);
const { result } = (await response.json()) as { result: string };
expect(BigInt(result)).toBeGreaterThan(0);
});

it("returns result for mina_getBalance", async () => {
Expand All @@ -22,23 +22,23 @@ it("returns result for mina_getBalance", async () => {
params: ["B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS"],
},
});
const { result } = await response.json();
const { result } = (await response.json()) as { result: string };
expect(BigInt(String(result))).toBeGreaterThan(0);
});

it("returns result for mina_blockHash", async () => {
const response = await client.api.$post({
json: { method: "mina_blockHash" },
});
const { result } = await response.json();
expect((result as unknown as string).length).toBeGreaterThan(0);
const { result } = (await response.json()) as { result: string };
expect(result.length).toBeGreaterThan(0);
});

it("returns result for mina_chainId", async () => {
const response = await client.api.$post({
json: { method: "mina_chainId" },
});
const { result } = await response.json();
const { result } = (await response.json()) as { result: string };
expect(result.length).toBeGreaterThan(0);
});

Expand All @@ -49,7 +49,8 @@ it("returns result for mina_getAccount", async () => {
params: ["B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS"],
},
});
const { result } = await response.json();
// biome-ignore lint/suspicious/noExplicitAny: TODO
const { result } = (await response.json()) as any;
expect(BigInt(result.nonce)).toBeGreaterThanOrEqual(0);
expect(BigInt(result.balance)).toBeGreaterThanOrEqual(0);
});
91 changes: 70 additions & 21 deletions apps/klesia/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { match } from "ts-pattern";
import mainDocs from "../docs/index.txt";
import rpcDocs from "../docs/rpc.txt";
import { mina } from "./methods/mina";
import { RpcMethodSchema, RpcResponseSchema } from "./schema";
import { RpcMethod, RpcMethodSchema, RpcResponseSchema } from "./schema";
import { buildResponse } from "./utils/build-response";

export const api = new OpenAPIHono();
Expand Down Expand Up @@ -63,43 +63,92 @@ const rpcRoute = createRoute({
export const klesiaRpcRoute = api.openapi(rpcRoute, async ({ req, json }) => {
const body = req.valid("json");
return match(body)
.with({ method: "mina_getTransactionCount" }, async ({ params }) => {
const [publicKey] = params;
const result = await mina.getTransactionCount({
publicKey: PublicKeySchema.parse(publicKey),
});
return json(buildResponse(result), 200);
})
.with({ method: "mina_getBalance" }, async ({ params }) => {
.with(
{ method: RpcMethod.enum.mina_getTransactionCount },
async ({ params }) => {
const [publicKey] = params;
const result = await mina.getTransactionCount({
publicKey: PublicKeySchema.parse(publicKey),
});
return json(
buildResponse({
method: RpcMethod.enum.mina_getTransactionCount,
result,
}),
200,
);
},
)
.with({ method: RpcMethod.enum.mina_getBalance }, async ({ params }) => {
const [publicKey] = params;
const result = await mina.getBalance({
publicKey: PublicKeySchema.parse(publicKey),
});
return json(buildResponse(result), 200);
return json(
buildResponse({ method: RpcMethod.enum.mina_getBalance, result }),
200,
);
})
.with({ method: "mina_blockHash" }, async () => {
.with({ method: RpcMethod.enum.mina_blockHash }, async () => {
if (process.env.MINA_NETWORK === "zeko_devnet") {
return json(
buildResponse({
method: RpcMethod.enum.mina_blockHash,
error: {
code: -32600,
message: "Network not supported.",
},
}),
200,
);
}
const result = await mina.blockHash();
return json(buildResponse(result), 200);
return json(
buildResponse({ method: RpcMethod.enum.mina_blockHash, result }),
200,
);
})
.with({ method: "mina_chainId" }, async () => {
.with({ method: RpcMethod.enum.mina_chainId }, async () => {
const result = await mina.chainId();
return json(buildResponse(result), 200);
})
.with({ method: "mina_sendTransaction" }, async ({ params }) => {
const [signedTransaction, type] = params;
const result = await mina.sendTransaction({ signedTransaction, type });
return json(buildResponse(result), 200);
return json(
buildResponse({ method: RpcMethod.enum.mina_chainId, result }),
200,
);
})
.with({ method: "mina_getAccount" }, async ({ params }) => {
.with(
{ method: RpcMethod.enum.mina_sendTransaction },
async ({ params }) => {
const [signedTransaction, type] = params;
const result = await mina.sendTransaction({ signedTransaction, type });
return json(
buildResponse({
method: RpcMethod.enum.mina_sendTransaction,
result,
}),
200,
);
},
)
.with({ method: RpcMethod.enum.mina_getAccount }, async ({ params }) => {
const [publicKey] = params;
const result = await mina.getAccount({
publicKey: PublicKeySchema.parse(publicKey),
});
return json(buildResponse(result), 200);
return json(
buildResponse({ method: RpcMethod.enum.mina_getAccount, result }),
200,
);
})
.exhaustive();
});

serve(api);

export type KlesiaRpc = typeof klesiaRpcRoute;
export {
KlesiaNetwork,
RpcMethod,
type RpcMethodType,
type RpcResponseType,
type RpcErrorType,
} from "./schema";
2 changes: 1 addition & 1 deletion apps/klesia/src/methods/mina.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const TEST_PKEY = "B62qkYa1o6Mj6uTTjDQCob7FYZspuhkm4RRQhgJg9j4koEBWiSrTQrS";

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

it("should return balance", async () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/klesia/src/methods/mina.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const getTransactionCount = async ({ publicKey }: { publicKey: string }) => {
`,
{ publicKey },
);
return Number.parseInt(data.account.nonce);
return data.account.nonce;
};

const getBalance = async ({ publicKey }: { publicKey: string }) => {
Expand Down
72 changes: 64 additions & 8 deletions apps/klesia/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,94 @@
import { PublicKeySchema } from "@mina-js/shared";
import { z } from "zod";

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

export const RpcMethod = z.enum([
"mina_getTransactionCount",
"mina_getBalance",
"mina_blockHash",
"mina_chainId",
"mina_sendTransaction",
"mina_getAccount",
]);
export type RpcMethodType = z.infer<typeof RpcMethod>;

export const RpcMethodSchema = z.discriminatedUnion("method", [
z.object({
method: z.literal("mina_getTransactionCount"),
method: z.literal(RpcMethod.enum.mina_getTransactionCount),
params: PublicKeyParamsSchema,
}),
z.object({
method: z.literal("mina_getBalance"),
method: z.literal(RpcMethod.enum.mina_getBalance),
params: PublicKeyParamsSchema,
}),
z.object({
method: z.literal("mina_blockHash"),
method: z.literal(RpcMethod.enum.mina_blockHash),
params: EmptyParamsSchema,
}),
z.object({
method: z.literal("mina_chainId"),
method: z.literal(RpcMethod.enum.mina_chainId),
params: EmptyParamsSchema,
}),
z.object({
method: z.literal("mina_sendTransaction"),
method: z.literal(RpcMethod.enum.mina_sendTransaction),
params: SendTransactionSchema,
}),
z.object({
method: z.literal("mina_getAccount"),
method: z.literal(RpcMethod.enum.mina_getAccount),
params: PublicKeyParamsSchema,
}),
]);

export const RpcResponseSchema = z.object({
export const JsonRpcResponse = z.object({
jsonrpc: z.literal("2.0"),
result: z.any(),
});

export const RpcError = z.object({
code: z.number(),
message: z.string(),
});

export type RpcErrorType = z.infer<typeof RpcError>;

export const ErrorSchema = JsonRpcResponse.extend({
error: RpcError,
});

export const RpcResponseSchema = z.union([
z.discriminatedUnion("method", [
JsonRpcResponse.extend({
method: z.literal(RpcMethod.enum.mina_getTransactionCount),
result: z.string(),
}),
JsonRpcResponse.extend({
method: z.literal(RpcMethod.enum.mina_getBalance),
result: z.string(),
}),
JsonRpcResponse.extend({
method: z.literal(RpcMethod.enum.mina_blockHash),
result: z.string(),
}),
JsonRpcResponse.extend({
method: z.literal(RpcMethod.enum.mina_chainId),
result: z.string(),
}),
JsonRpcResponse.extend({
method: z.literal(RpcMethod.enum.mina_sendTransaction),
result: z.string(),
}),
JsonRpcResponse.extend({
method: z.literal(RpcMethod.enum.mina_getAccount),
result: z.object({
nonce: z.string(),
balance: z.string(),
}),
}),
]),
ErrorSchema,
]);

export type RpcResponseType = z.infer<typeof RpcResponseSchema>;
20 changes: 16 additions & 4 deletions apps/klesia/src/utils/build-response.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
export const buildResponse = (data: unknown) => ({
jsonrpc: "2.0",
result: data,
});
import type { RpcErrorType, RpcMethodType } from "../schema";

export const buildResponse = ({
result,
error,
method,
}: { result?: unknown; error?: RpcErrorType; method: RpcMethodType }) => {
if (error) {
return {
jsonrpc: "2.0",
error,
method,
};
}
return { jsonrpc: "2.0", result, method };
};
9 changes: 6 additions & 3 deletions apps/klesia/src/utils/node.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Client, cacheExchange, fetchExchange } from "@urql/core";
import { match } from "ts-pattern";
import { z } from "zod";
import { KlesiaNetwork } from "../schema";

const NetworkMatcher = z.enum(["devnet", "mainnet"]);

const MINA_NETWORK = NetworkMatcher.parse(process.env.MINA_NETWORK ?? "devnet");
const MINA_NETWORK = KlesiaNetwork.parse(process.env.MINA_NETWORK ?? "devnet");
const NODE_API_DEVNET = z
.string()
.parse(
Expand All @@ -17,11 +16,15 @@ const NODE_API_MAINNET = z
process.env.NODE_API_MAINNET ??
"https://api.minascan.io/node/mainnet/v1/graphql",
);
const NODE_API_ZEKO_DEVNET = z
.string()
.parse(process.env.NODE_API_ZEKO_DEVNET ?? "https://devnet.zeko.io/graphql");

export const getNodeApiUrl = () => {
return match(MINA_NETWORK)
.with("devnet", () => NODE_API_DEVNET)
.with("mainnet", () => NODE_API_MAINNET)
.with("zeko_devnet", () => NODE_API_ZEKO_DEVNET)
.exhaustive();
};

Expand Down
Binary file modified bun.lockb
Binary file not shown.
Loading

0 comments on commit 8fdaf93

Please sign in to comment.