@vlayer/sdk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json",
3
+ "changelog": "@changesets/cli/changelog",
4
+ "commit": false,
5
+ "fixed": [],
6
+ "linked": [],
7
+ "access": "restricted",
8
+ "baseBranch": "main",
9
+ "updateInternalDependencies": "patch",
10
+ "ignore": []
11
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @vlayer/sdk
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - added changesets
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @vlayer/sdk
2
+
3
+ ## Install / usage
4
+ Check our docs at our [Vanilla JS/TS docs](https://book.vlayer.xyz/javascript/javascript.html)
5
+
6
+ ## Bumping version
7
+ Learn more at our [JS contribution docs](https://book.vlayer.xyz/contributing/javascript.htm)
@@ -0,0 +1,22 @@
1
+ import js from "@eslint/js";
2
+ import ts from "typescript-eslint";
3
+ import prettierRecommended from "eslint-plugin-prettier/recommended";
4
+ import globals from "globals";
5
+
6
+ export default [
7
+ js.configs.recommended,
8
+ ...ts.configs.recommended,
9
+ prettierRecommended,
10
+ {
11
+ rules: {
12
+ "no-unused-vars": "warn",
13
+ "no-undef": "warn",
14
+ },
15
+ languageOptions: {
16
+ globals: {
17
+ ...globals.browser,
18
+ Bun: false,
19
+ },
20
+ },
21
+ },
22
+ ];
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@vlayer/sdk",
3
+ "type": "module",
4
+ "module": "src/index.ts",
5
+ "version": "0.0.1",
6
+ "scripts": {
7
+ "build": "npm run gen:types",
8
+ "test": "vitest --run",
9
+ "gen:types": "sh ../bash/build_ts_types.sh"
10
+ },
11
+ "devDependencies": {
12
+ "@changesets/cli": "^2.27.7",
13
+ "@types/bun": "latest",
14
+ "@types/mailparser": "^3.4.4",
15
+ "abitype": "^1.0.6",
16
+ "vitest": "^2.1.1"
17
+ },
18
+ "peerDependencies": {
19
+ "typescript": "^5.0.0"
20
+ },
21
+ "dependencies": {
22
+ "mailparser": "^3.7.1",
23
+ "viem": "^2.21.0"
24
+ }
25
+ }
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { resolveDkimDns } from "./dnsResolver.ts";
3
+
4
+ describe("resolveDkimDns Integration", () => {
5
+ test("resolves VLayer DNS", async () => {
6
+ const resolved = await resolveDkimDns(
7
+ "vlayer-xyz.20230601.gappssmtp.com",
8
+ "20230601",
9
+ );
10
+ const expected =
11
+ "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3gWcOhCm99qzN+h7/2+LeP3CLsJkQQ4EP/2mrceXle5pKq8uZmBl1U4d2Vxn4w+pWFANDLmcHolLboESLFqEL5N6ae7u9b236dW4zn9AFkXAGenTzQEeif9VUFtLAZ0Qh2eV7OQgz/vPj5IaNqJ7h9hpM9gO031fe4v+J0DLCE8Rgo7hXbNgJavctc0983DaCDQaznHZ44LZ6TtZv9TBs+QFvsy4+UCTfsuOtHzoEqOOuXsVXZKLP6B882XbEnBpXEF8QzV4J26HiAJFUbO3mAqZL2UeKC0hhzoIZqZXNG0BfuzOF0VLpDa18GYMUiu+LhEJPJO9D8zhzvQIHNrpGwIDAQAB";
12
+ expect(resolved).toBe(expected);
13
+ });
14
+
15
+ test("throws error if dns not found", async () => {
16
+ await expect(resolveDkimDns("not-a-domain.com", "abcd")).rejects.toThrow();
17
+ });
18
+ });
@@ -0,0 +1,14 @@
1
+ import dns from "node:dns";
2
+
3
+ export async function resolveDkimDns(domain: string, selector: string) {
4
+ return new Promise<string>((resolve, reject) => {
5
+ dns.resolveTxt(`${selector}._domainkey.${domain}`, (err, addresses) => {
6
+ if (err) {
7
+ reject(err);
8
+ return;
9
+ }
10
+
11
+ resolve(addresses.flat()[0]);
12
+ });
13
+ });
14
+ }
@@ -0,0 +1,105 @@
1
+ import assert from "node:assert";
2
+ import { describe, expect, test } from "vitest";
3
+ import { getDkimSigners, parseEmail } from "./parseEmail.ts";
4
+ import { StructuredHeader } from "mailparser";
5
+
6
+ const emailHeaders = `From: "John Doe" <john@d.oe>
7
+ To: "Jane Doe" <jane@d.oe>
8
+ Subject: Hello World
9
+ Date: Thu, 1 Jan 1970 00:00:00 +0000
10
+ `;
11
+
12
+ const dkimHeader =
13
+ "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; h=from:to:subject; s=selector1; b=abcdef;";
14
+
15
+ const body = "Hello, World!";
16
+
17
+ const emailFixture = `${emailHeaders}${dkimHeader}\n\n${body}`;
18
+
19
+ describe("parseEmail", () => {
20
+ test("should get dkim header from email", async () => {
21
+ const email = await parseEmail(emailFixture);
22
+ const dkim = email.headers.get("dkim-signature")!;
23
+ assert(typeof dkim === "object" && "params" in dkim);
24
+ expect(dkim.params.d).toBe("example.com");
25
+ expect(dkim.params.s).toBe("selector1");
26
+ });
27
+
28
+ test("correctly parses untrimmed email", async () => {
29
+ const untrimmed = `\n ${emailFixture} \n`;
30
+ const email = await parseEmail(untrimmed);
31
+ expect(email.headers.get("dkim-signature")).toBeDefined();
32
+ });
33
+
34
+ test("works well with multiple dkim headers", async () => {
35
+ const dkimHeader2 =
36
+ "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=second.signer; h=from:to:subject; s=selector2; b=abcdef;";
37
+
38
+ const email = await parseEmail(
39
+ `${emailHeaders}${dkimHeader}\n${dkimHeader2}\n\n${body}`,
40
+ );
41
+ const dkim = email.headers.get(
42
+ "dkim-signature",
43
+ )! as unknown as StructuredHeader[];
44
+ expect(dkim).toHaveLength(2);
45
+ expect(dkim[0].params.s).toBe("selector1");
46
+ expect(dkim[1].params.s).toBe("selector2");
47
+ });
48
+ });
49
+
50
+ describe("getDkimSigners", () => {
51
+ test("should get dkim signers from email", async () => {
52
+ const email = await parseEmail(emailFixture);
53
+ const dkim = getDkimSigners(email);
54
+ expect(dkim).toHaveLength(1);
55
+ expect(dkim[0].domain).toBe("example.com");
56
+ expect(dkim[0].selector).toBe("selector1");
57
+ });
58
+
59
+ test("should get multiple dkim signers from email", async () => {
60
+ const dkimHeader2 =
61
+ "DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=second.signer; h=from:to:subject; s=selector2; b=abcdef;";
62
+ const email = await parseEmail(
63
+ `${emailHeaders}${dkimHeader}\n${dkimHeader2}\n\n${body}`,
64
+ );
65
+
66
+ const dkim = getDkimSigners(email);
67
+ expect(dkim).toHaveLength(2);
68
+ expect(dkim[0].domain).toBe("example.com");
69
+ expect(dkim[0].selector).toBe("selector1");
70
+ expect(dkim[1].domain).toBe("second.signer");
71
+ expect(dkim[1].selector).toBe("selector2");
72
+ });
73
+
74
+ test("should throw if no dkim header found", async () => {
75
+ const email = await parseEmail(emailHeaders);
76
+ expect(() => getDkimSigners(email)).toThrowError("No DKIM header found");
77
+ });
78
+
79
+ test("should throw if dkim header is invalid", async () => {
80
+ const email = await parseEmail(
81
+ `${emailHeaders}DKIM-Signature: invalid\n\n${body}`,
82
+ );
83
+ expect(() => getDkimSigners(email)).toThrowError(
84
+ "DKIM header missing domain",
85
+ );
86
+ });
87
+
88
+ test("should throw if dkim header is missing domain", async () => {
89
+ const email = await parseEmail(
90
+ `${emailHeaders}DKIM-Signature: v=1; s=selector\n\n${body}`,
91
+ );
92
+ expect(() => getDkimSigners(email)).toThrowError(
93
+ "DKIM header missing domain",
94
+ );
95
+ });
96
+
97
+ test("should throw if dkim header is missing selector", async () => {
98
+ const email = await parseEmail(
99
+ `${emailHeaders}DKIM-Signature: v=1; d=example.com\n\n${body}`,
100
+ );
101
+ expect(() => getDkimSigners(email)).toThrowError(
102
+ "DKIM header missing selector",
103
+ );
104
+ });
105
+ });
@@ -0,0 +1,45 @@
1
+ import { simpleParser, HeaderValue, type ParsedMail } from "mailparser";
2
+
3
+ export class DkimParsingError extends Error {
4
+ constructor(message: string) {
5
+ super(message);
6
+ this.name = "DkimParsingError";
7
+ }
8
+ }
9
+
10
+ export async function parseEmail(mime: string) {
11
+ return simpleParser(mime.trim(), {
12
+ skipHtmlToText: true,
13
+ skipTextToHtml: true,
14
+ skipTextLinks: true,
15
+ skipImageLinks: true,
16
+ });
17
+ }
18
+
19
+ export function getDkimSigners(mail: ParsedMail) {
20
+ const dkimHeader = mail.headers.get("dkim-signature");
21
+ if (!dkimHeader) throw new DkimParsingError("No DKIM header found");
22
+ if (Array.isArray(dkimHeader)) {
23
+ return dkimHeader.map(parseHeader);
24
+ }
25
+ return [parseHeader(dkimHeader)];
26
+ }
27
+
28
+ function parseHeader(header: HeaderValue) {
29
+ if (typeof header === "string" || !("params" in header)) {
30
+ throw new DkimParsingError(`Invalid DKIM header ${header}`);
31
+ }
32
+
33
+ if (!header.params.d) {
34
+ throw new DkimParsingError("DKIM header missing domain");
35
+ }
36
+
37
+ if (!header.params.s) {
38
+ throw new DkimParsingError("DKIM header missing selector");
39
+ }
40
+
41
+ return {
42
+ domain: header.params.d,
43
+ selector: header.params.s,
44
+ };
45
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { preverifyEmail } from "./preverify.ts";
3
+ import { readFile } from "testHelpers/readFile";
4
+
5
+ describe("Preverify email: integration", () => {
6
+ test("adds dns record to email mime", async () => {
7
+ const rawEmail = readFile("./src/api/email/testdata/test_email.txt");
8
+ const preverifiedEmail = await preverifyEmail(rawEmail);
9
+ expect(preverifiedEmail).toMatchObject({
10
+ email: rawEmail,
11
+ dnsRecords: [expect.stringContaining("v=DKIM1; k=rsa; p=")],
12
+ });
13
+ });
14
+
15
+ test("throws error if DKIM not found", async () => {
16
+ const emailWithNoDkimHeader = 'From: "Alice"\n\nBody';
17
+ await expect(preverifyEmail(emailWithNoDkimHeader)).rejects.toThrow(
18
+ "No DKIM header found",
19
+ );
20
+ });
21
+
22
+ test("throws error if DNS could not be resolved", async () => {
23
+ const emailWithNoDkimHeader = readFile(
24
+ "./src/api/email/testdata/test_email_unknown_domain.txt",
25
+ );
26
+ await expect(preverifyEmail(emailWithNoDkimHeader)).rejects.toThrow();
27
+ });
28
+
29
+ test("throws error if multiple DNS records found", async () => {
30
+ const emailWithNoDkimHeader = readFile(
31
+ "./src/api/email/testdata/test_email_multiple_dkims.txt",
32
+ );
33
+ await expect(preverifyEmail(emailWithNoDkimHeader)).rejects.toThrow(
34
+ "Multiple DKIM headers found",
35
+ );
36
+ });
37
+ });
@@ -0,0 +1,19 @@
1
+ import { parseEmail, getDkimSigners } from "./parseEmail.ts";
2
+ import { resolveDkimDns } from "./dnsResolver.ts";
3
+
4
+ export async function preverifyEmail(mimeEmail: string) {
5
+ const parsedEmail = await parseEmail(mimeEmail);
6
+ const signers = getDkimSigners(parsedEmail);
7
+ if (signers.length === 0) {
8
+ throw new Error("No DKIM header found");
9
+ }
10
+ if (signers.length > 1) {
11
+ throw new Error("Multiple DKIM headers found");
12
+ }
13
+ const [{ domain, selector }] = signers;
14
+ const dnsRecord = await resolveDkimDns(domain, selector);
15
+ return {
16
+ email: mimeEmail,
17
+ dnsRecords: [dnsRecord],
18
+ };
19
+ }
@@ -0,0 +1,21 @@
1
+ DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
2
+ c=simple/simple; d=google.com;
3
+ h=Received:From:To:Subject:Date:Message-ID; i=joe@football.example.com;
4
+ s=20230601; t=1615825284; v=1;
5
+ b=Xh4Ujb2wv5x54gXtulCiy4C0e+plRm6pZ4owF+kICpYzs/8WkTVIDBrzhJP0DAYCpnL62T0G
6
+ k+0OH8pi/yqETVjKtKk+peMnNvKkut0GeWZMTze0bfq3/JUK3Ln3jTzzpXxrgVnvBxeY9EZIL4g
7
+ s4wwFRRKz/1bksZGSjD8uuSU=
8
+ Received: from client1.football.example.com [192.0.2.1]
9
+ by submitserver.example.com with SUBMISSION;
10
+ Fri, 11 Jul 2003 21:01:54 -0700 (PDT)
11
+ From: Joe SixPack <joe@football.example.com>
12
+ To: Suzie Q <suzie@shopping.example.net>
13
+ Subject: Is dinner ready?
14
+ Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
15
+ Message-ID: <20030712040037.46341.5F8J@football.example.com>
16
+
17
+ Hi.
18
+
19
+ We lost the game. Are you hungry yet?
20
+
21
+ Joe.
@@ -0,0 +1,28 @@
1
+ DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
2
+ c=simple/simple; d=google.com;
3
+ h=Received:From:To:Subject:Date:Message-ID; i=joe@football.example.com;
4
+ s=20230601; t=1615825284; v=1;
5
+ b=Xh4Ujb2wv5x54gXtulCiy4C0e+plRm6pZ4owF+kICpYzs/8WkTVIDBrzhJP0DAYCpnL62T0G
6
+ k+0OH8pi/yqETVjKtKk+peMnNvKkut0GeWZMTze0bfq3/JUK3Ln3jTzzpXxrgVnvBxeY9EZIL4g
7
+ s4wwFRRKz/1bksZGSjD8uuSU=
8
+ DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
9
+ c=simple/simple; d=google.com;
10
+ h=Received:From:To:Subject:Date:Message-ID; i=joe@football.example.com;
11
+ s=20230601; t=1615825284; v=1;
12
+ b=Xh4Ujb2wv5x54gXtulCiy4C0e+plRm6pZ4owF+kICpYzs/8WkTVIDBrzhJP0DAYCpnL62T0G
13
+ k+0OH8pi/yqETVjKtKk+peMnNvKkut0GeWZMTze0bfq3/JUK3Ln3jTzzpXxrgVnvBxeY9EZIL4g
14
+ s4wwFRRKz/1bksZGSjD8uuSU=
15
+ Received: from client1.football.example.com [192.0.2.1]
16
+ by submitserver.example.com with SUBMISSION;
17
+ Fri, 11 Jul 2003 21:01:54 -0700 (PDT)
18
+ From: Joe SixPack <joe@football.example.com>
19
+ To: Suzie Q <suzie@shopping.example.net>
20
+ Subject: Is dinner ready?
21
+ Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
22
+ Message-ID: <20030712040037.46341.5F8J@football.example.com>
23
+
24
+ Hi.
25
+
26
+ We lost the game. Are you hungry yet?
27
+
28
+ Joe.
@@ -0,0 +1,21 @@
1
+ DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
2
+ c=simple/simple; d=foobar.xyz;
3
+ h=Received:From:To:Subject:Date:Message-ID; i=joe@football.example.com;
4
+ s=wertyu; t=1615825284; v=1;
5
+ b=Xh4Ujb2wv5x54gXtulCiy4C0e+plRm6pZ4owF+kICpYzs/8WkTVIDBrzhJP0DAYCpnL62T0G
6
+ k+0OH8pi/yqETVjKtKk+peMnNvKkut0GeWZMTze0bfq3/JUK3Ln3jTzzpXxrgVnvBxeY9EZIL4g
7
+ s4wwFRRKz/1bksZGSjD8uuSU=
8
+ Received: from client1.football.example.com [192.0.2.1]
9
+ by submitserver.example.com with SUBMISSION;
10
+ Fri, 11 Jul 2003 21:01:54 -0700 (PDT)
11
+ From: Joe SixPack <joe@football.example.com>
12
+ To: Suzie Q <suzie@shopping.example.net>
13
+ Subject: Is dinner ready?
14
+ Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
15
+ Message-ID: <20030712040037.46341.5F8J@football.example.com>
16
+
17
+ Hi.
18
+
19
+ We lost the game. Are you hungry yet?
20
+
21
+ Joe.
@@ -0,0 +1,169 @@
1
+ import {
2
+ type Abi,
3
+ type Address,
4
+ type ContractFunctionArgs,
5
+ type ContractFunctionName,
6
+ createTestClient,
7
+ type Hex,
8
+ http,
9
+ HttpTransport,
10
+ publicActions,
11
+ PublicClient,
12
+ walletActions,
13
+ } from "viem";
14
+
15
+ import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
16
+ import { foundry } from "viem/chains";
17
+
18
+ import type { ContractSpec, ContractArg } from "types/ethereum";
19
+
20
+ export const testChainId1 = 100001;
21
+ export const testChainId2 = 100002;
22
+
23
+ const rpcUrls: Map<number, HttpTransport> = new Map([
24
+ [testChainId1, http()],
25
+ [testChainId2, http("http://127.0.0.1:8546")],
26
+ ]);
27
+
28
+ export const chainIds = [testChainId1, testChainId2];
29
+
30
+ export function client(
31
+ chainId: number = testChainId1,
32
+ ): ReturnType<typeof walletActions> & PublicClient {
33
+ const transport = rpcUrls.get(chainId);
34
+ if (transport == undefined) {
35
+ throw Error(`No url for chainId ${chainId}`);
36
+ }
37
+
38
+ return createTestClient({
39
+ chain: foundry,
40
+ mode: "anvil",
41
+ transport: transport,
42
+ })
43
+ .extend(publicActions)
44
+ .extend(walletActions);
45
+ }
46
+
47
+ export async function deployContract(
48
+ contractSpec: ContractSpec,
49
+ args: ContractArg[] = [],
50
+ chainId: number = testChainId1,
51
+ ): Promise<Address> {
52
+ const ethClient = client(chainId);
53
+
54
+ const [deployer] = await ethClient.getAddresses();
55
+
56
+ const txHash = await ethClient.deployContract({
57
+ abi: contractSpec.abi,
58
+ bytecode: contractSpec.bytecode.object,
59
+ account: deployer,
60
+ args,
61
+ chain: foundry,
62
+ });
63
+
64
+ const receipt = await ethClient.waitForTransactionReceipt({ hash: txHash });
65
+
66
+ if (receipt.status != "success") {
67
+ throw new Error(
68
+ `Contract deployment failed with status: ${receipt.status}`,
69
+ );
70
+ }
71
+
72
+ return receipt.contractAddress as Address;
73
+ }
74
+
75
+ type DeploySpec<T extends Abi> = {
76
+ abi: T;
77
+ bytecode: {
78
+ object: Hex;
79
+ };
80
+ };
81
+
82
+ type Tail<T> = T extends readonly [unknown, ...infer U] ? U : [];
83
+
84
+ export async function deployProverVerifier<P extends Abi, V extends Abi>(
85
+ proverSpec: DeploySpec<P>,
86
+ verifierSpec: DeploySpec<V>,
87
+ args: {
88
+ prover?: ContractArg[];
89
+ verifier?: Tail<ContractArg>[];
90
+ } = {},
91
+ chainId: number = testChainId1,
92
+ ) {
93
+ console.log("Deploying prover");
94
+ const proverAddress = await deployContract(
95
+ proverSpec,
96
+ args.prover ?? [],
97
+ chainId,
98
+ );
99
+ console.log(`Prover has been deployed on ${proverAddress} address`);
100
+
101
+ console.log("Deploying verifier");
102
+ const verifierAddress = await deployContract(
103
+ verifierSpec,
104
+ [proverAddress, ...(args.verifier ?? [])],
105
+ chainId,
106
+ );
107
+ console.log(`Verifier has been deployed on ${verifierAddress} address`);
108
+
109
+ return [proverAddress, verifierAddress];
110
+ }
111
+
112
+ export async function call<
113
+ T extends Abi,
114
+ F extends ContractFunctionName<T, "pure" | "view">,
115
+ >(
116
+ abi: T,
117
+ address: Address,
118
+ functionName: F,
119
+ args?: ContractFunctionArgs<T, "pure" | "view", F>,
120
+ chainId: number = testChainId1,
121
+ ) {
122
+ const ethClient = client(chainId);
123
+
124
+ return ethClient.readContract({
125
+ abi,
126
+ address,
127
+ functionName,
128
+ args,
129
+ });
130
+ }
131
+
132
+ export async function writeContract<
133
+ T extends Abi,
134
+ F extends ContractFunctionName<T, "payable" | "nonpayable">,
135
+ >(
136
+ address: Address,
137
+ abi: T,
138
+ functionName: F,
139
+ args: ContractFunctionArgs<T, "payable" | "nonpayable", F>,
140
+ sender?: Address,
141
+ chainId: number = testChainId1,
142
+ ) {
143
+ const ethClient = client(chainId);
144
+ const selectedSender = sender || (await ethClient.getAddresses())[0];
145
+
146
+ const txHash = await ethClient.writeContract({
147
+ abi,
148
+ address,
149
+ functionName,
150
+ args,
151
+ account: selectedSender,
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
+ } as any);
154
+ // TODO: fix any to viem type
155
+
156
+ const txReceipt = await ethClient.waitForTransactionReceipt({ hash: txHash });
157
+
158
+ if (txReceipt.status != "success") {
159
+ throw new Error(`Transaction failed with status: ${txReceipt.status}`);
160
+ }
161
+
162
+ return txReceipt;
163
+ }
164
+
165
+ export const getTestAccount = () => privateKeyToAccount(generatePrivateKey());
166
+
167
+ export const getTestAddresses = (
168
+ chainId: number = testChainId1,
169
+ ): Promise<Address[]> => client(chainId).getAddresses();
@@ -0,0 +1,18 @@
1
+ import { VlayerClient } from "types/vlayer";
2
+ import { WebProofProvider } from "types/webProof";
3
+
4
+ export const createVlayerClient = ({
5
+ url,
6
+ webProofProvider,
7
+ }: {
8
+ url: string;
9
+ webProofProvider: WebProofProvider;
10
+ }): VlayerClient => {
11
+ return {
12
+ prove: async () => {
13
+ console.log("prove");
14
+ console.log("url", url);
15
+ console.log("webProofProvider", webProofProvider);
16
+ },
17
+ };
18
+ };
@@ -0,0 +1,43 @@
1
+ import { Abi, Address, Hex } from "viem";
2
+ import { Branded } from "./utils";
3
+ export type Bytecode = {
4
+ object: Hex;
5
+ };
6
+
7
+ export type ContractSpec = {
8
+ abi: Abi;
9
+ bytecode: Bytecode;
10
+ };
11
+
12
+ export type ContractArg =
13
+ | number
14
+ | string
15
+ | boolean
16
+ | bigint
17
+ | Address
18
+ | number[]
19
+ | string[]
20
+ | boolean[]
21
+ | bigint[]
22
+ | Address[];
23
+
24
+ export type EthereumAddress = Branded<Hex, "EthereumAddress">;
25
+ export type EthereumTxHash = Branded<Hex, "EthereumTxHash">;
26
+
27
+ export function assertEthereumAddress(
28
+ hash: string,
29
+ ): asserts hash is EthereumAddress {
30
+ const regex = /^(0x)?[0-9a-fA-F]{40}$/;
31
+ if (!regex.test(hash)) {
32
+ throw new Error(`Invalid ethereum account ${hash}`);
33
+ }
34
+ }
35
+
36
+ export function assertEthereumTxHash(
37
+ hash: string,
38
+ ): asserts hash is EthereumTxHash {
39
+ const regex = /^(0x)?[0-9a-fA-F]{64}$/;
40
+ if (!regex.test(hash)) {
41
+ throw new Error(`Invalid ethereum tx hash ${hash}`);
42
+ }
43
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./ethereum";
2
+ export * from "./webProof";
@@ -0,0 +1,3 @@
1
+ declare const __brand: unique symbol;
2
+ type Brand<B> = { [__brand]: B };
3
+ export type Branded<T, B> = T & Brand<B>;
@@ -0,0 +1,55 @@
1
+ import { Address, Hex } from "viem";
2
+
3
+ type Calldata = string;
4
+
5
+ export type CallParams = {
6
+ to: Address;
7
+ data: Calldata;
8
+ };
9
+
10
+ export type CallContext = {
11
+ chain_id: number; // 31337
12
+ };
13
+
14
+ export interface Proof {
15
+ length: bigint;
16
+ seal: {
17
+ verifierSelector: Hex;
18
+ seal: [Hex, Hex, Hex, Hex, Hex, Hex, Hex, Hex];
19
+ mode: number;
20
+ };
21
+ dynamicParamsOffsets: [
22
+ bigint,
23
+ bigint,
24
+ bigint,
25
+ bigint,
26
+ bigint,
27
+ bigint,
28
+ bigint,
29
+ bigint,
30
+ bigint,
31
+ bigint,
32
+ ];
33
+ commitment: {
34
+ proverContractAddress: Address;
35
+ functionSelector: Hex;
36
+ settleBlockHash: Hex;
37
+ settleBlockNumber: bigint;
38
+ };
39
+ }
40
+
41
+ export interface VCallResult {
42
+ evm_call_result: Hex;
43
+ proof: Proof;
44
+ }
45
+
46
+ export interface VCallResponse {
47
+ jsonrpc: string;
48
+ result: VCallResult;
49
+ id: number;
50
+ }
51
+
52
+ // Add more methods here
53
+ export type VlayerClient = {
54
+ prove: () => void;
55
+ };
@@ -0,0 +1,106 @@
1
+ //NOTE : this is copied from tlsn-js 5.4
2
+ // for some reason newest vertsions doesnt not export this type (clarification is in progress)
3
+ // probaly it should be reexported from tlsn-js
4
+
5
+ export interface WebProof {
6
+ session: Session;
7
+ substrings: Substrings;
8
+ notaryUrl: string;
9
+ }
10
+
11
+ export interface Session {
12
+ header: Header;
13
+ signature: Signature;
14
+ session_info: SessionInfo;
15
+ }
16
+
17
+ export interface SessionInfo {
18
+ server_name: ServerName;
19
+ handshake_decommitment: HandshakeDecommitment;
20
+ }
21
+
22
+ export interface HandshakeDecommitment {
23
+ nonce: number[];
24
+ data: Data;
25
+ }
26
+
27
+ export interface Data {
28
+ server_cert_details: ServerCERTDetails;
29
+ server_kx_details: ServerKxDetails;
30
+ client_random: number[];
31
+ server_random: number[];
32
+ }
33
+
34
+ export interface ServerCERTDetails {
35
+ cert_chain: Array<number[]>;
36
+ ocsp_response: number[];
37
+ scts: null;
38
+ }
39
+
40
+ export interface ServerKxDetails {
41
+ kx_params: number[];
42
+ kx_sig: KxSig;
43
+ }
44
+
45
+ export interface KxSig {
46
+ scheme: string;
47
+ sig: number[];
48
+ }
49
+
50
+ export interface Header {
51
+ encoder_seed: number[];
52
+ merkle_root: number[];
53
+ sent_len: number;
54
+ recv_len: number;
55
+ handshake_summary: HandshakeSummary;
56
+ }
57
+
58
+ export interface HandshakeSummary {
59
+ time: number;
60
+ server_public_key: ServerPublicKey;
61
+ handshake_commitment: number[];
62
+ }
63
+
64
+ export interface ServerPublicKey {
65
+ group: string;
66
+ key: number[];
67
+ }
68
+
69
+ export interface ServerName {
70
+ Dns: string;
71
+ }
72
+
73
+ export interface Signature {
74
+ P256: string;
75
+ }
76
+
77
+ export interface Substrings {
78
+ openings: { [key: string]: Opening[] };
79
+ inclusion_proof: InclusionProof;
80
+ }
81
+
82
+ export interface InclusionProof {
83
+ proof: unknown[];
84
+ total_leaves: number;
85
+ }
86
+
87
+ export interface Opening {
88
+ kind?: string;
89
+ ranges?: Range[];
90
+ direction?: string;
91
+ Blake3?: Blake3;
92
+ }
93
+
94
+ export interface Blake3 {
95
+ data: number[];
96
+ nonce: number[];
97
+ }
98
+
99
+ export interface Range {
100
+ start: number;
101
+ end: number;
102
+ }
103
+
104
+ export type WebProofProvider = {
105
+ requestProof: (url: string) => Promise<WebProof>;
106
+ };
@@ -0,0 +1,73 @@
1
+ import {
2
+ type Abi,
3
+ AbiFunction,
4
+ AbiStateMutability,
5
+ type Address,
6
+ ContractFunctionArgs,
7
+ ContractFunctionName,
8
+ decodeFunctionResult,
9
+ encodeFunctionData,
10
+ } from "viem";
11
+
12
+ import { type CallContext, type CallParams, Proof } from "types/vlayer";
13
+ import { v_call } from "./v_call";
14
+ import { testChainId1 } from "./helpers";
15
+ import { ContractSpec } from "types/ethereum";
16
+
17
+ export async function getContractSpec(file: string): Promise<ContractSpec> {
18
+ return Bun.file(file).json();
19
+ }
20
+
21
+ export async function prove<
22
+ T extends readonly [AbiFunction, ...Abi[number][]],
23
+ F extends ContractFunctionName<T>,
24
+ >(
25
+ prover: Address,
26
+ abi: T,
27
+ functionName: F,
28
+ args: ContractFunctionArgs<T, AbiStateMutability, F>,
29
+ chainId = testChainId1,
30
+ ) {
31
+ const calldata = encodeFunctionData({
32
+ abi: abi as Abi,
33
+ functionName: functionName as string,
34
+ args: args as readonly unknown[],
35
+ });
36
+
37
+ const call: CallParams = { to: prover, data: calldata };
38
+ const context: CallContext = {
39
+ chain_id: chainId,
40
+ };
41
+
42
+ const {
43
+ result: { proof, evm_call_result },
44
+ } = await v_call(call, context);
45
+
46
+ const returnValue = decodeFunctionResult({
47
+ abi: abi as Abi,
48
+ data: evm_call_result,
49
+ functionName: functionName as string,
50
+ });
51
+
52
+ addDynamicParamsOffsets(abi, functionName, proof);
53
+
54
+ return { proof, returnValue: returnValue as `0x${string}`[] };
55
+ }
56
+
57
+ function addDynamicParamsOffsets(
58
+ abi: Abi,
59
+ functionName: string | undefined,
60
+ proof: Proof,
61
+ ) {
62
+ const proverFunction = abi.find(
63
+ (f) => f.type === "function" && f.name === functionName,
64
+ ) as AbiFunction;
65
+
66
+ if (proverFunction?.outputs && proverFunction.outputs.length > 0) {
67
+ const secondVerifyMethodParamType = proverFunction.outputs[0].type;
68
+
69
+ if (secondVerifyMethodParamType === "string") {
70
+ proof.dynamicParamsOffsets[0] = BigInt(32);
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,44 @@
1
+ import { CallContext, CallParams, VCallResponse } from "types/vlayer";
2
+
3
+ function v_callBody(call: CallParams, context: CallContext) {
4
+ return {
5
+ method: "v_call",
6
+ params: [call, context],
7
+ id: 1,
8
+ jsonrpc: "2.0",
9
+ };
10
+ }
11
+
12
+ export async function v_call(
13
+ call: CallParams,
14
+ context: CallContext,
15
+ ): Promise<VCallResponse> {
16
+ const response = await fetch("http://127.0.0.1:3000", {
17
+ method: "POST",
18
+ body: JSON.stringify(v_callBody(call, context)),
19
+ headers: { "Content-Type": "application/json" },
20
+ });
21
+
22
+ if (!response.ok) {
23
+ throw new Error(`HTTP error! status: ${response.status}`);
24
+ }
25
+
26
+ const response_json = await response.json();
27
+
28
+ //TODO we should launch some schema validation here
29
+ assertObject(response_json);
30
+
31
+ if ("error" in response_json) {
32
+ throw new Error(
33
+ `Error response: ${(response_json.error as { message: string }).message || "unknown error"}`,
34
+ );
35
+ }
36
+
37
+ return response_json as Promise<VCallResponse>;
38
+ }
39
+
40
+ function assertObject(x: unknown): asserts x is object {
41
+ if (typeof x !== "object") {
42
+ throw new Error("Expected object");
43
+ }
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { v_call } from "./api/v_call";
2
+ export type { CallParams, CallContext } from "types/vlayer";
3
+ export type { ContractSpec } from "types/ethereum";
4
+
5
+ export { getContractSpec, prove } from "./api/prover";
6
+ export * as testHelpers from "./api/helpers";
7
+ export { client as createTestClient } from "./api/helpers";
8
+ export { preverifyEmail } from "./api/email/preverify.ts";
@@ -0,0 +1,3 @@
1
+ import fs from "fs";
2
+
3
+ export const readFile = (path: string) => fs.readFileSync(path).toString();
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": [
4
+ "ESNext"
5
+ ],
6
+ "module": "esnext",
7
+ "target": "esnext",
8
+ "moduleResolution": "bundler",
9
+ "moduleDetection": "force",
10
+ "allowImportingTsExtensions": true,
11
+ "strict": true,
12
+ "downlevelIteration": true,
13
+ "skipLibCheck": true,
14
+ "jsx": "preserve",
15
+ "allowSyntheticDefaultImports": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "allowJs": true,
18
+ "noEmit": true,
19
+ "composite": true,
20
+ "types": [
21
+ "bun" // add Bun global
22
+ ],
23
+ "baseUrl": "./",
24
+ "paths": {
25
+ "types/*": ["./src/api/lib/types/*"],
26
+ "testHelpers/*": ["./src/testHelpers/*"],
27
+ },
28
+
29
+ }
30
+ }