Skip to content

Commit

Permalink
feat(client-lib): update client lib
Browse files Browse the repository at this point in the history
  • Loading branch information
zcabter committed Jan 22, 2025
1 parent a2eb62d commit 34372a1
Show file tree
Hide file tree
Showing 8 changed files with 2,920 additions and 2,570 deletions.
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

0 comments on commit 34372a1

Please sign in to comment.