Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client-lib): update client lib #774

Merged
merged 4 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/jstz_proto/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ pub struct RunFunction {
pub method: Method,
/// Any valid HTTP headers
#[serde(with = "http_serde::header_map")]
#[schema(schema_with= openapi::http_headers)]
#[schema(schema_with = openapi::http_headers)]
pub headers: HeaderMap,
#[schema(schema_with = openapi::http_body_schema)]
pub body: HttpBody,
Expand Down
2 changes: 1 addition & 1 deletion examples/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@jstz-dev/sdk": "^0.0.0",
"@jstz-dev/sdk": "file:../../packages/sdk",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"vite-plugin-top-level-await": "^1.4.1",
Expand Down
4 changes: 2 additions & 2 deletions examples/dashboard/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { Jstz, User } from "@jstz-dev/sdk";

const DEFAULT_ENDPOINT = "localhost:8933";
const DEFAULT_ENDPOINT = "http://localhost:8933";

const SignUp: React.FC<{ addUser: (name: string, user: User) => void }> = ({
addUser,
Expand Down Expand Up @@ -150,7 +150,7 @@ const RunSmartFunction: React.FC<{ endpoint: string; user: User }> = ({

const runFunction = async () => {
const result = await new Jstz(endpoint).run(user, { uri });
setFunctionResult(result.statusCode);
setFunctionResult(result.status_code);
};

return (
Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@
python39 # for running web-platform tests
]
++ lib.optionals stdenv.isLinux [pkg-config openssl.dev]
++ lib.optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [Security SystemConfiguration]);
++ lib.optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [Security SystemConfiguration Foundation]);
};
}
);
Expand Down
5,185 changes: 2,860 additions & 2,325 deletions package-lock.json

Large diffs are not rendered by default.

289 changes: 50 additions & 239 deletions packages/sdk/index.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,9 @@
import * as jstz from "jstz_sdk";

namespace ffi {
export type Address = { Tz1: string };

export type Signature = { Ed25519: string };

export type PublicKey = { Ed25519: string };

export type Operation = {
source: Address;
nonce: number;
content: OperationContent;
};

export type Headers = Record<string, string>;
export type Body = Uint8Array;

export type OperationContent =
| { DeployFunction: { function_code: string; account_credit: number } }
| {
RunFunction: {
uri: string;
method: string;
headers: Headers;
body: Body | null;
gas_limit: number;
};
};

export type SignedOperation = {
public_key: PublicKey;
signature: Signature;
inner: Operation;
};

export type Receipt = {
hash: Uint8Array;
inner: ReceiptResult;
};

export type ReceiptResult = { Ok: ReceiptContent } | { Err: string };

export type ReceiptContent =
| {
RunFunction: {
body: Body;
status_code: number;
headers: Headers;
};
}
| {
DeployFunction: {
address: Address;
};
};
}
// FIXME: https://linear.app/tezos/issue/JSTZ-287/publish-client-lib-to-jstz-dev-scope
// Change to import from "@jstz-dev/client" when published to scope
import { Jstz as JstzClient } from "@zcabter/client";
import JstzType from "@zcabter/client";

export type Address = string;

Expand All @@ -64,12 +13,6 @@ export function isAddress(value: unknown): value is Address {
return typeof value === "string" && value.match(ADDRESS_REGEX) !== null;
}

interface Operation {
source: Address;
nonce: number;
content: OperationContent;
}

export type JstzHeaders = Record<string, string>;
export type JstzBody = Uint8Array;
export type JstzRequest = {
Expand All @@ -85,152 +28,29 @@ export type JstzResponse = {
body: JstzBody;
};

type OperationContent =
| {
kind: "deploy";
functionCode: string;
initialBalance: number;
}
| ({
kind: "run";
} & JstzRequest);

export type User = {
address: Address;
publicKey: string;
secretKey: string;
};

type SignedOperation = {
publicKey: string;
signature: string;
hash: string;
operation: Operation;
};

const encodeAddress = (address: Address): ffi.Address => {
return { Tz1: address };
};

const encodeSignature = (signature: string): ffi.Signature => {
return { Ed25519: signature };
};

const encodePublicKey = (publicKey: string): ffi.PublicKey => {
return { Ed25519: publicKey };
};

const encodeOperationContent = (
content: OperationContent,
): ffi.OperationContent => {
switch (content.kind) {
case "deploy":
return {
DeployFunction: {
function_code: content.functionCode,
account_credit: content.initialBalance,
},
};
case "run":
return {
RunFunction: {
uri: content.uri,
method: content.method || "GET",
headers: content.headers || {},
body: content.body === undefined ? null : content.body,
gas_limit: content.gasLimit || 1000,
},
};
}
};

const encodeOperation = (operation: Operation): ffi.Operation => {
const { source, nonce, content } = operation;

return {
source: encodeAddress(source),
nonce,
content: encodeOperationContent(content),
};
};

const encodeSignedOperation = (
signedOperation: SignedOperation,
): ffi.SignedOperation => {
const { publicKey, signature, operation } = signedOperation;

return {
public_key: encodePublicKey(publicKey),
signature: encodeSignature(signature),
inner: encodeOperation(operation),
};
};

const signOperation = (user: User, operation: Operation): SignedOperation => {
const ffiOperation = encodeOperation(operation);

const signature = jstz.sign_operation(ffiOperation, user.secretKey);
const hash = jstz.hash_operation(ffiOperation);

return { publicKey: user.publicKey, signature, hash, operation };
const signOperation = (
user: User,
operation: JstzType.Operation,
): JstzType.Signature => {
const signature = jstz.sign_operation(operation, user.secretKey);
return signature;
};

export class Jstz {
private endpoint: string;
private client: JstzClient;

constructor(endpoint: string) {
this.endpoint = endpoint;
this.client = new JstzClient({ baseURL: endpoint });
}

async getNonce(source: Address): Promise<number> {
const res = await fetch(`http://${this.endpoint}/accounts/${source}/nonce`);

if (res.status === 404) {
return 0;
}

if (res.status !== 200) {
console.log(res);
throw new Error("Failed to fetch nonce");
}

return (await res.json()) as number;
}

private pollReceipt(hash: string): Promise<ffi.Receipt> {
const endpoint = this.endpoint;
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
try {
const res = await fetch(
`http://${endpoint}/operations/${hash}/receipt`,
);
if (res.status === 200) {
const receipt = (await res.json()) as ffi.Receipt;
clearInterval(interval);
resolve(receipt);
}
} catch (err) {
clearInterval(interval);
reject(err);
}
}, 1000);
});
}

private async postSignedOperation(
operation: SignedOperation,
): Promise<ffi.Receipt> {
await fetch(`http://${this.endpoint}/operations`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(encodeSignedOperation(operation)),
});

const receipt = await this.pollReceipt(operation.hash);

return receipt;
return this.client.accounts.getNonce(source);
}

async deploy(
Expand All @@ -239,64 +59,55 @@ export class Jstz {
initialBalance: number = 0,
): Promise<Address> {
const nonce = await this.getNonce(user.address);
const content: JstzType.Operation.DeployFunction = {
_type: "DeployFunction",
function_code: functionCode,
account_credit: initialBalance,
};

const operation: Operation = {
const operation = {
source: user.address,
nonce,
content: {
kind: "deploy",
functionCode,
initialBalance,
},
content,
};

const receipt = await this.postSignedOperation(
signOperation(user, operation),
);

if ("Err" in receipt.inner) {
throw new Error(receipt.inner.Err);
}

const receiptContent = receipt.inner["Ok"];

if (!("DeployFunction" in receiptContent)) {
throw new Error("Unexpected receipt kind");
}

return receiptContent.DeployFunction.address.Tz1;
const signature = signOperation(user, operation);
const request = {
public_key: user.publicKey,
signature: signature,
inner: operation,
};
const receipt = await this.client.operations.injectAndPoll(request);
return receipt.result.inner.address;
}

async run(user: User, request: JstzRequest): Promise<JstzResponse> {
async run(
user: User,
request: JstzRequest,
): Promise<JstzType.Receipt.Success.RunFunction> {
const nonce = await this.getNonce(user.address);
const content: JstzType.Operation.RunFunction = {
_type: "RunFunction",
body: request.body ? Array.from(request.body) : null,
gas_limit: request.gasLimit ?? 1000,
headers: request.headers ?? {},
method: request.method ?? "GET",
uri: request.uri,
};

const operation: Operation = {
const operation = {
source: user.address,
nonce,
content: {
kind: "run",
...request,
},
content,
};

const receipt = await this.postSignedOperation(
signOperation(user, operation),
);

if ("Err" in receipt.inner) {
throw new Error(receipt.inner.Err);
}
const signature = signOperation(user, operation);

const receiptContent = receipt.inner["Ok"];

if (!("RunFunction" in receiptContent)) {
throw new Error("Unexpected receipt kind");
}
const receipt = await this.client.operations.injectAndPoll({
public_key: user.publicKey,
signature: signature,
inner: operation,
});

return {
statusCode: receiptContent.RunFunction.status_code,
headers: receiptContent.RunFunction.headers,
body: receiptContent.RunFunction.body,
};
return receipt.result.inner;
}
}
1 change: 1 addition & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"build": "tsc --outDir dist"
},
"dependencies": {
"@zcabter/client": "0.1.0-alpha.6",
"jstz_sdk": "file:../../crates/jstz_sdk/pkg"
}
}
Loading
Loading