feat: add SDK package (#12)

This PR introduces the template for the SDK package.
This commit is contained in:
moebius
2025-01-08 13:30:40 +00:00
committed by GitHub
parent 9d7173c9a7
commit b81ed7bc34
25 changed files with 2036 additions and 136 deletions

View File

@@ -1,6 +1,13 @@
name: CI
name: Circuits
on: [push]
on:
push:
paths:
- "packages/circuits/**"
pull_request:
branches: [main, dev]
paths:
- "packages/circuits/**"
concurrency:
group: ${{github.workflow}}-${{github.ref}}
@@ -12,7 +19,7 @@ defaults:
jobs:
test:
name: Run Circuits Tests
name: Tests
runs-on: ubuntu-latest
steps:

View File

@@ -1,6 +1,13 @@
name: CI
name: Contracts
on: [push]
on:
push:
paths:
- "packages/contracts/**"
pull_request:
branches: [main, dev]
paths:
- "packages/contracts/**"
concurrency:
group: ${{github.workflow}}-${{github.ref}}
@@ -16,7 +23,7 @@ env:
jobs:
unit-tests:
name: Run Unit Tests
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -43,7 +50,7 @@ jobs:
run: yarn test:unit
integration-tests:
name: Run Integration Tests
name: Integration Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -69,7 +76,7 @@ jobs:
run: yarn test:integration
halmos-tests:
name: Run symbolic execution tests
name: Symbolic execution tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -120,3 +127,69 @@ jobs:
run: yarn --frozen-lockfile --network-concurrency 1
- run: yarn lint:check
upload-coverage:
name: Upload Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: "yarn"
- name: Install dependencies
run: yarn --frozen-lockfile --network-concurrency 1
- name: Run coverage
shell: bash
run: yarn coverage
- name: Setup LCOV
uses: hrishikesh-kadam/setup-lcov@v1
- name: Filter directories
run: lcov --remove lcov.info 'test/*' 'script/*' --output-file lcovNew.info --rc lcov_branch_coverage=1 --rc derive_function_end_line=0 --ignore-errors unused
- name: Capture coverage output
id: new-coverage
uses: zgosalvez/github-actions-report-lcov@v4
with:
coverage-files: lcovNew.info
- name: Retrieve previous coverage
uses: actions/download-artifact@v4
with:
name: coverage.info
continue-on-error: true
- name: Check if a previous coverage exists
run: |
if [ ! -f coverage.info ]; then
echo "Artifact not found. Initializing at 0"
echo "0" >> coverage.info
fi
- name: Compare previous coverage
run: |
old=$(cat coverage.info)
new=$(( ${{ steps.new-coverage.outputs.total-coverage }} + ${{ env.COVERAGE_SENSITIVITY_PERCENT }} ))
if [ "$new" -lt "$old" ]; then
echo "Coverage decreased from $old to $new"
exit 1
fi
mv lcovNew.info coverage.info
- name: Upload the new coverage
uses: actions/upload-artifact@v4
with:
name: coverage.info
path: ./coverage.info

View File

@@ -1,77 +0,0 @@
name: Coverage Check
on: [push]
env:
COVERAGE_SENSITIVITY_PERCENT: 1
defaults:
run:
working-directory: packages/contracts
jobs:
upload-coverage:
name: Upload Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: "yarn"
- name: Install dependencies
run: yarn --frozen-lockfile --network-concurrency 1
- name: Run coverage
shell: bash
run: yarn coverage
- name: Setup LCOV
uses: hrishikesh-kadam/setup-lcov@v1
- name: Filter directories
run: lcov --remove lcov.info 'test/*' 'script/*' --output-file lcovNew.info --rc lcov_branch_coverage=1 --rc derive_function_end_line=0 --ignore-errors unused
- name: Capture coverage output
id: new-coverage
uses: zgosalvez/github-actions-report-lcov@v4
with:
coverage-files: lcovNew.info
- name: Retrieve previous coverage
uses: actions/download-artifact@v4
with:
name: coverage.info
continue-on-error: true
- name: Check if a previous coverage exists
run: |
if [ ! -f coverage.info ]; then
echo "Artifact not found. Initializing at 0"
echo "0" >> coverage.info
fi
- name: Compare previous coverage
run: |
old=$(cat coverage.info)
new=$(( ${{ steps.new-coverage.outputs.total-coverage }} + ${{ env.COVERAGE_SENSITIVITY_PERCENT }} ))
if [ "$new" -lt "$old" ]; then
echo "Coverage decreased from $old to $new"
exit 1
fi
mv lcovNew.info coverage.info
- name: Upload the new coverage
uses: actions/upload-artifact@v4
with:
name: coverage.info
path: ./coverage.info

53
.github/workflows/sdk.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: SDK
on:
push:
paths:
- "packages/sdk/**"
pull_request:
branches: [main, dev]
paths:
- "packages/sdk/**"
concurrency:
group: ${{github.workflow}}-${{github.ref}}
cancel-in-progress: true
defaults:
run:
working-directory: packages/sdk
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Check out github repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install dependencies
run: yarn --frozen-lockfile --network-concurrency 1
- name: Run Build
run: yarn build
- name: Check types
run: yarn check-types
unit:
name: Unit tests
runs-on: ubuntu-latest
steps:
- name: Check out github repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run tests with coverage
run: yarn test:cov

View File

@@ -1,13 +1,14 @@
{
"name": "privacy-pool-core",
"version": "1.0.0",
"version": "0.1.0",
"description": "Core repository for the Privacy Pool protocol circuits and smart contracts",
"repository": "https://github.com/defi-wonderland/privacy-pool-core",
"author": "Defi Wonderland",
"license": "MIT",
"author": "Wonderland",
"private": true,
"workspaces": [
"packages/circuits",
"packages/contracts"
"packages/contracts",
"packages/sdk"
]
}

View File

@@ -1,7 +1,10 @@
{
"name": "@privacy-pool-core/circuits",
"version": "1.0.0",
"version": "0.1.0",
"description": "Circom circuits for the Privacy Pool protocol",
"repository": "https://github.com/defi-wonderland/privacy-pool-core",
"license": "MIT",
"author": "Wonderland",
"scripts": {
"compile": "npx ts-node ./src/index.ts",
"test": "npx mocha",
@@ -25,9 +28,9 @@
},
"devDependencies": {
"@types/mocha": "^10.0.1",
"@types/node": "^20.3.0",
"@types/node": "20.3.1",
"mocha": "^10.2.0",
"ts-node": "^10.9.1",
"typescript": "^5.1.3"
"typescript": "5.5.4"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@privacy-pool-core/contracts",
"version": "1.0.0",
"version": "0.1.0",
"description": "Solidity smart contracts for the Privacy Pool protocol",
"repository": "https://github.com/defi-wonderland/privacy-pool-core",
"license": "MIT",

56
packages/sdk/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# See https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored for more about ignoring yarn files.
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
*.tsbuildinfo
# Dependencies
node_modules
# Local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Build Outputs
out/
build
dist
cache
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
.DS_Store
*.pem
coverage
# Turbo
.turbo
# Foundry
contracts/out
contracts/cache
# Temporary files
.tmp
# IDEA
.idea
# vscode
.vscode
# Coverage
lcov.info

66
packages/sdk/README.md Normal file
View File

@@ -0,0 +1,66 @@
# ts-turborepo-boilerplate: sample-lib package
> Note: use this lib as reference but preferred way is to re-write package
> from zero instead of refactoring this one.
> When you don't need this anymore, you can delete it
Sample library that exposes a Blockchain provider to query
account balances on Ethereum mainnet or EVM-compatible blockchains
## Setup
1. Change package name to your own in [`package.json`](./package.json)
2. Install dependencies running `pnpm install`
## Available Scripts
Available scripts that can be run using `pnpm`:
| Script | Description |
| ------------- | ------------------------------------------------------- |
| `build` | Build library using tsc |
| `check-types` | Check types issues using tsc |
| `clean` | Remove `dist` folder |
| `lint` | Run ESLint to check for coding standards |
| `lint:fix` | Run linter and automatically fix code formatting issues |
| `format` | Check code formatting and style using Prettier |
| `format:fix` | Run formatter and automatically fix issues |
| `test` | Run tests using vitest |
| `test:cov` | Run tests with coverage report |
## Usage
### Importing the Package
You can import the package in your TypeScript or JavaScript files as follows:
```typescript
import { BlockchainProvider } from "@ts-turborepo-boilerplate/sample-lib";
```
### Example
```typescript
// EVM-provider
const rpcUrl = ""; //non-empty valid url
const address = "0x...";
const provider = new BlockchainProvider(rpcUrl);
const balance = await provider.getBalance(address);
console.log(`Balance of ${address} is ${balance}`);
```
## API
### [IBlockchainProvider](./src/interfaces/blockchainProvider.interface.ts)
Available methods
- `getBalance(address: Address)`
## References
- [Viem](https://viem.sh/)
- [Offchain docs: Internal module pattern](https://www.notion.so/defi-wonderland/Best-Practices-c08b71f28e59490f8dadef64cf61c9ac?pvs=4#89f99d33053a426285bacc6275d994c0)

52
packages/sdk/package.json Normal file
View File

@@ -0,0 +1,52 @@
{
"name": "@privacy-pool-core/sdk",
"version": "0.1.0",
"private": true,
"description": "Typescript SDK for the Privacy Pool protocol",
"repository": "https://github.com/defi-wonderland/privacy-pool-core",
"license": "MIT",
"author": "Wonderland",
"type": "module",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"directories": {
"src": "src"
},
"files": [
"dist/*",
"package.json",
"!**/*.tsbuildinfo"
],
"scripts": {
"build": "tsc -p tsconfig.build.json",
"check-types": "tsc --noEmit -p ./tsconfig.json",
"clean": "rm -rf dist",
"format": "prettier --check \"{src,test}/**/*.{js,ts,json}\"",
"format:fix": "prettier --write \"{src,test}/**/*.{js,ts,json}\"",
"lint": "eslint \"{src,test}/**/*.{js,ts,json}\"",
"lint:fix": "eslint --fix",
"test": "vitest run --config vitest.config.ts --passWithNoTests",
"test:cov": "vitest run --config vitest.config.ts --coverage"
},
"dependencies": {
"viem": "2.21.4"
},
"devDependencies": {
"@commitlint/config-conventional": "19.4.1",
"@ianvs/prettier-plugin-sort-imports": "4.3.1",
"@types/node": "20.3.1",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.18.0",
"@vitest/coverage-v8": "2.0.5",
"commitlint": "19.4.1",
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.2.1",
"husky": "9.1.5",
"lint-staged": "15.2.10",
"prettier": "3.3.3",
"sort-package-json": "2.10.1",
"typescript": "5.5.4",
"vitest": "2.0.5"
}
}

View File

@@ -0,0 +1 @@
export * from "./invalidRpcUrl.exception.js";

View File

@@ -0,0 +1,6 @@
export class InvalidRpcUrl extends Error {
constructor(url: string) {
super(`${url} is invalid`);
this.name = "InvalidRpcUrl";
}
}

View File

@@ -0,0 +1,5 @@
export type { IBlockchainProvider, Address } from "./internal.js";
export { InvalidRpcUrl } from "./internal.js";
export { BlockchainProvider } from "./internal.js";

View File

@@ -0,0 +1 @@
export * from "./external.js";

View File

@@ -0,0 +1,13 @@
import { Address } from "../internal.js";
/**
* Represents an interface for a blockchain provider.
*/
export interface IBlockchainProvider {
/**
* Retrieves the balance of the specified address.
* @param {Address} address The address for which to retrieve the balance.
* @returns {Promise<bigint>} A Promise that resolves to the balance of the address.
*/
getBalance(address: Address): Promise<bigint>;
}

View File

@@ -0,0 +1 @@
export * from "./blockchainProvider.interface.js";

View File

@@ -0,0 +1,4 @@
export type { Address } from "viem";
export * from "./exceptions/index.js";
export * from "./interfaces/index.js";
export * from "./providers/index.js";

View File

@@ -0,0 +1,26 @@
import type { Address, Chain, HttpTransport } from "viem";
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
import { IBlockchainProvider, InvalidRpcUrl } from "../internal.js";
export class BlockchainProvider implements IBlockchainProvider {
private client: ReturnType<typeof createPublicClient<HttpTransport, Chain>>;
constructor(rpcUrl: string) {
// dummy check for the rpcUrl
if (!rpcUrl || !rpcUrl.startsWith("http")) {
throw new InvalidRpcUrl(rpcUrl);
}
this.client = createPublicClient({
chain: mainnet,
transport: http(rpcUrl),
});
}
/** @inheritdoc */
async getBalance(address: Address): Promise<bigint> {
return this.client.getBalance({ address });
}
}

View File

@@ -0,0 +1 @@
export * from "./blockchainProvider.js";

View File

@@ -0,0 +1,52 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { BlockchainProvider, InvalidRpcUrl } from "../../src/internal.js";
const mockClient = {
getBalance: vi.fn(),
};
vi.mock("viem", async (importOriginal) => {
const actual = await importOriginal<typeof import("viem")>();
return {
...actual,
createPublicClient: vi.fn().mockImplementation(() => mockClient),
http: vi.fn(),
};
});
describe("BlockchainProvider", () => {
let blockchainProvider: BlockchainProvider;
beforeEach(() => {
blockchainProvider = new BlockchainProvider("http://example.com/rpc");
});
afterEach(() => {
vi.clearAllMocks();
});
describe("constructor", () => {
it("creates a client with the specified rpcUrl", () => {
const provider = new BlockchainProvider("http://example.com/rpc");
expect(provider).toBeDefined();
expect(provider["client"]).toBeDefined();
});
it("throws an error for an invalid rpcUrl", () => {
expect(() => new BlockchainProvider("invalid-url")).toThrow(InvalidRpcUrl);
});
});
describe("getBalance", () => {
it("returns the balance for a valid address", async () => {
const address = "0x1234567890abcdef";
const expectedBalance = BigInt(1000000000000000000);
vi.spyOn(mockClient, "getBalance").mockResolvedValue(expectedBalance);
const result = await blockchainProvider.getBalance(address);
expect(result).toEqual(expectedBalance);
});
});
});

View File

@@ -0,0 +1,27 @@
/* Based on total-typescript no-dom app config */
/* https://github.com/total-typescript/tsconfig */
{
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
"verbatimModuleSyntax": false,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
"useUnknownInCatchVariables": true,
"forceConsistentCasingInFileNames": true,
"noImplicitOverride": true,
/* If transpiling with TypeScript: */
"module": "NodeNext",
"sourceMap": true,
/* If your code doesn't run in the DOM: */
"lib": ["es2022"],
"outDir": "./dist"
}
}

View File

@@ -0,0 +1,9 @@
/* Based on total-typescript no-dom library config */
/* https://github.com/total-typescript/tsconfig */
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"incremental": true,
"noEmit": false
}
}

View File

@@ -0,0 +1,5 @@
{
"extends": "./tsconfig.base.json",
"include": ["**/*", ".*.js"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,22 @@
import path from "path";
import { configDefaults, defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true, // Use Vitest's global API without importing it in each file
environment: "node", // Use the Node.js environment
include: ["test/**/*.spec.ts"], // Include test files
exclude: ["node_modules", "dist"], // Exclude certain directories
coverage: {
provider: "v8",
reporter: ["text", "json", "html"], // Coverage reporters
exclude: ["node_modules", "dist", "src/index.ts", ...configDefaults.exclude], // Files to exclude from coverage
},
},
resolve: {
alias: {
// Setup path alias based on tsconfig paths
"@": path.resolve(__dirname, "src"),
},
},
});

1581
yarn.lock

File diff suppressed because it is too large Load Diff