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

integrations/wagmi: initial re-work of wagmi support #292

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ada5a12
integrations/wagmi: initial re-work of wagmi support
CedarMist Mar 22, 2024
2f3c44d
updated pnpm-lock.yaml
CedarMist Mar 22, 2024
881c8f6
wagmi: added docs & minor cleanup
CedarMist Mar 23, 2024
66e41e3
Try global 'fetch' function if not defined in globalThis
CedarMist Mar 23, 2024
3547e56
clients/js: removed --no-experimental-fetch from CI tests
CedarMist Mar 23, 2024
9b67e2d
clients/js: updated docs & changelog
CedarMist Mar 23, 2024
72ed436
clients/js: formatting
CedarMist Mar 23, 2024
195e537
examples/wagmi: added visualizer to output
CedarMist Mar 23, 2024
2155379
Add sapphire testnet to wagmi example
CedarMist Mar 26, 2024
01f81dd
examples: formatting of wagmi example
CedarMist Mar 29, 2024
64afe5d
wagmi: renamed Wagmi related packages `-v2` + addressed PR comments
CedarMist Apr 2, 2024
7511296
integrations/wagmi-v2: biome linting config
CedarMist Apr 2, 2024
bd3ce31
wagmi: removed wagmi from docs, it has its own section in integration…
CedarMist Apr 2, 2024
372fe95
wagmi: formatting
CedarMist Apr 2, 2024
3adfc65
wagmi: removed wagmi from docs
CedarMist Apr 2, 2024
6045522
wagmi/viem: added hardhat-viem example and fixed TX serializer
CedarMist Apr 4, 2024
ad207a2
ci: added wagmi & viem to CI + fixed lint errors
CedarMist Apr 4, 2024
77fe9c6
ci: fixes
CedarMist Apr 4, 2024
5edc5a6
clients/js: fixed formatting
CedarMist Apr 4, 2024
30d0a52
viem: CI test to verify encryption
CedarMist Apr 4, 2024
4143067
viem: moved viem tests further up CI
CedarMist Apr 4, 2024
4ca20e2
wagmi: lint + CI step reorg
CedarMist Apr 5, 2024
862aa35
viem: fixed proxy tests
CedarMist Apr 5, 2024
9f9bfac
wagmi & viem: updated dependencies so peer types work correctly with …
CedarMist Apr 5, 2024
ee112fd
wagmi: reorder tests + lint for CI
CedarMist Apr 5, 2024
241d339
wagmi: build supports alpha release now
CedarMist Apr 6, 2024
11e75ea
feedback from PR #292
CedarMist Apr 8, 2024
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: 0 additions & 2 deletions .github/workflows/ci-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,6 @@ jobs:
clients/js/lib

- name: Test JS client
env:
NODE_OPTIONS: "--no-experimental-fetch" # https://github.com/nock/nock/issues/2397
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
run: pnpm test

test-integration-hardhat:
Expand Down
36 changes: 29 additions & 7 deletions .github/workflows/contracts-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
--rm
--health-cmd="/oasis-node debug control wait-ready -a unix:/serverdir/node/net-runner/network/client-0/internal.sock"
--health-start-period=90s
env:
SAPPHIRE_LOCALNET_HTTP_PROXY_PORT: 3001
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down Expand Up @@ -54,10 +56,6 @@ jobs:
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Install forge doc deps
run: cargo install mdbook-pagetoc
- name: Install dependencies
run: pnpm install
- name: Build JS client
Expand All @@ -66,12 +64,29 @@ jobs:
- name: Build hardhat integration
working-directory: integrations/hardhat
run: pnpm build
- name: Build Viem integration
working-directory: integrations/viem-v2
run: pnpm build
- name: Build Wagmi integration
working-directory: integrations/wagmi-v2
run: pnpm build
- uses: JarvusInnovations/background-action@v1
name: RPC proxy will error if non-encrypted calls are made
with:
run: pnpm run proxy &
wait-on: http://127.0.0.1:${{ env.SAPPHIRE_LOCALNET_HTTP_PROXY_PORT }}
tail: true
log-output-resume: true
wait-for: 30sec
log-output: true
working-directory: clients/js
- name: hardhat-viem
working-directory: examples/hardhat-viem
run: pnpm hardhat test --network sapphire-localnet

- name: Test contracts with Hardhat
working-directory: contracts
run: pnpm hardhat test --network sapphire-localnet-ci
- name: Build docs
working-directory: contracts
run: pnpm doc
- name: hardhat test examples/hardhat
working-directory: examples/hardhat
run: pnpm hardhat run --network sapphire-localnet scripts/run-vigil.ts
Expand All @@ -86,3 +101,10 @@ jobs:
- name: ethersv6-ts-esm
working-directory: examples/ethersv6-ts-esm
run: pnpm run test
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Install forge doc deps
run: cargo install mdbook-pagetoc
- name: Build docs
working-directory: contracts
run: pnpm doc
22 changes: 18 additions & 4 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ name: publish
on:
push:
tags:
- clients/js/v[0-9].[0-9]+.[0-9]+
- contracts/v[0-9].[0-9]+.[0-9]+
- integrations/hardhat/v[0-9].[0-9]+.[0-9]+
- clients/js/v[0-9]\.[0-9]+\.[0-9]+(-(alpha|beta|rc)(\.[0-9]+)?)?
- contracts/v[0-9]\.[0-9]+\.[0-9]+(-(alpha|beta|rc)(\.[0-9]+)?)?
- integrations/hardhat/v[0-9]\.[0-9]+\.[0-9]+(-(alpha|beta|rc)(\.[0-9]+)?)?
- integrations/viem-v2/v[0-9]\.[0-9]+\.[0-9]+(-(alpha|beta|rc)(\.[0-9]+)?)?
- integrations/wagmi-v2/v[0-9]\.[0-9]+\.[0-9]+(-(alpha|beta|rc)(\.[0-9]+)?)?

jobs:
publish:
Expand All @@ -21,10 +23,22 @@ jobs:
with:
version: 8
run_install: true
- name: Build JS client
working-directory: clients/js
run: pnpm build
- name: Build hardhat integration
working-directory: integrations/hardhat
run: pnpm build
- name: Build Viem integration
working-directory: integrations/viem-v2
run: pnpm build
- name: Build Wagmi integration
working-directory: integrations/wagmi-v2
run: pnpm build
- name: Extract package from tag
id: extract-tag
run: |
echo "NPM_PACKAGE=$(echo ${{ github.ref_name }} | grep -oE '(clients/js|contracts|integrations/hardhat)')" >> $GITHUB_OUTPUT
echo "NPM_PACKAGE=$(echo ${{ github.ref_name }} | grep -oE '(clients/js|contracts|integrations/(hardhat|wagmi-v2|viem-v2))')" >> $GITHUB_OUTPUT
- name: Publish ${{ github.ref_name }} to NPM
uses: JS-DevTools/npm-publish@v3
with:
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,34 @@ This monorepo includes the source code for the following Sapphire packages:
| [Go client][go-pkg] | [![version][go-version]][go-pkg] | | |
| [Solidity smart contracts][contracts-npm] | [![version][contracts-version]][contracts-npm] | | ![downloads][contracts-downloads] |
| [Hardhat plugin][hardhat-npm] | [![version][hardhat-version]][hardhat-npm] | [![size][hardhat-size]][hardhat-bundlephobia] | ![downloads][hardhat-downloads] |
| [Wagmi 2.x support][wagmi-npm] | [![version][wagmi-version]][wagmi-npm] | [![size][wagmi-size]][wagmi-bundlephobia] | ![downloads][wagmi-downloads] |


[go-pkg]: https://pkg.go.dev/github.com/oasisprotocol/sapphire-paratime

[hardhat-npm]: https://www.npmjs.com/package/@oasisprotocol/sapphire-hardhat
[contracts-npm]: https://www.npmjs.com/package/@oasisprotocol/sapphire-contracts
[client-npm]: https://www.npmjs.com/package/@oasisprotocol/sapphire-paratime
[wagmi-npm]: https://www.npmjs.com/package/@oasisprotocol/sapphire-wagmi-v2

[go-version]: https://img.shields.io/github/go-mod/go-version/oasisprotocol/sapphire-paratime?filename=clients%2Fgo%2Fgo.mod
[hardhat-version]: https://img.shields.io/npm/v/@oasisprotocol/sapphire-hardhat
[contracts-version]: https://img.shields.io/npm/v/@oasisprotocol/sapphire-contracts
[client-version]: https://img.shields.io/npm/v/@oasisprotocol/sapphire-paratime
[wagmi-version]: https://img.shields.io/npm/v/@oasisprotocol/sapphire-wagmi-v2

[hardhat-size]: https://img.shields.io/bundlephobia/minzip/@oasisprotocol/sapphire-hardhat
[client-size]: https://img.shields.io/bundlephobia/minzip/@oasisprotocol/sapphire-paratime
[wagmi-size]: https://img.shields.io/bundlephobia/minzip/@oasisprotocol/sapphire-wagmi-v2

[hardhat-bundlephobia]: https://bundlephobia.com/package/@oasisprotocol/sapphire-hardhat
[client-bundlephobia]: https://bundlephobia.com/package/@oasisprotocol/sapphire-paratime
[wagmi-bundlephobia]: https://bundlephobia.com/package/@oasisprotocol/sapphire-wagmi-v2

[hardhat-downloads]: https://img.shields.io/npm/dm/@oasisprotocol/sapphire-hardhat.svg
[contracts-downloads]: https://img.shields.io/npm/dm/@oasisprotocol/sapphire-contracts.svg
[client-downloads]: https://img.shields.io/npm/dm/@oasisprotocol/sapphire-paratime.svg
[wagmi-downloads]: https://img.shields.io/npm/dm/@oasisprotocol/sapphire-wagmi-v2.svg

## Layout

Expand All @@ -50,7 +56,7 @@ the following directories:
- [`docs`](./docs): topic-oriented Sapphire documentation
- [`examples`](./examples/): sample code snippets in popular Ethereum
development environments
- [`integrations`](./integrations/): plugins with popular Solidity tools
- [`integrations`](./integrations/): plugins for popular Solidity tools
- [`runtime`](./runtime/): the Sapphire Paratime as based off of the
[Oasis SDK](https://github.com/oasisprotocol/oasis-sdk)

Expand Down
22 changes: 22 additions & 0 deletions clients/js/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ The format is inspired by [Keep a Changelog].

[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/

## 1.3.3 (2024-04-05)

### Added

- Wagmi v2 & Viem v2 support, with `hardhat-viem` & wagmi examples

### Fixed

- Calldata public key encryption/decryption routines are no longer async
- KeyFetcher has `runInBackground` method to eagerly fetch the keys

### Changed

- Supports only Node v18+

## 1.3.2 (2024-02-06)

### Changed

- Refactored calldata public key handling
- Added `epoch` support for calldata public key, which makes web UIs which are open for a long time more reliable

## 1.3.1 (2024-01-26)

### Fixed
Expand Down
3 changes: 1 addition & 2 deletions clients/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"type": "module",
"name": "@oasisprotocol/sapphire-paratime",
"license": "Apache-2.0",
"version": "1.3.2",
"version": "1.3.3",
"description": "The Sapphire ParaTime Web3 integration library.",
"homepage": "https://github.com/oasisprotocol/sapphire-paratime/tree/main/clients/js",
"repository": {
Expand Down Expand Up @@ -60,7 +60,6 @@
"eslint-config-prettier": "^8.5.0",
"jest": "^29.7.0",
"nock": "^13.4.0",
"node-fetch": "^2.6.7",
"prettier": "^2.7.1",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
Expand Down
17 changes: 13 additions & 4 deletions clients/js/scripts/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { decodeRlp, getBytes } from 'ethers';

import * as cborg from 'cborg';

import fetch from 'node-fetch';
import { assert } from 'console';

async function getBody(request: IncomingMessage): Promise<string> {
Expand All @@ -22,7 +21,9 @@ async function getBody(request: IncomingMessage): Promise<string> {
});
}

const LISTEN_PORT = 3000;
const LISTEN_PORT = process.env.SAPPHIRE_LOCALNET_HTTP_PROXY_PORT
? Number(process.env.SAPPHIRE_LOCALNET_HTTP_PROXY_PORT)
: 3001;
const DIE_ON_UNENCRYPTED = true;
const UPSTREAM_URL = 'http://127.0.0.1:8545';
const SHOW_ENCRYPTED_RESULTS = false;
Expand All @@ -43,7 +44,11 @@ interface JSONRPCRequest {

async function onRequest(req: IncomingMessage, response: ServerResponse) {
if (req.method !== 'POST') {
response.writeHead(500, 'Not POST!');
// An initial prefetch request will be made to determine if CORS is allowed.
lubej marked this conversation as resolved.
Show resolved Hide resolved
response.writeHead(200, 'Not POST!', {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
});
response.end();
return;
}
Expand Down Expand Up @@ -159,7 +164,11 @@ async function onRequest(req: IncomingMessage, response: ServerResponse) {
console.log(' - RESULT', pj);
}

response.writeHead(200, 'OK');
response.writeHead(200, 'OK', {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
});
response.write(JSON.stringify(pj));
response.end();
}
86 changes: 48 additions & 38 deletions clients/js/src/calldatapublickey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,37 +58,7 @@ function toCallDataPublicKey(
} as CallDataPublicKey;
}

// TODO: remove, this is unecessary, node has `fetch` now?
async function fetchRuntimePublicKeyNode(
gwUrl: string,
): Promise<RawCallDataPublicKeyResponse> {
// Import http or https, depending on the URI scheme.
const https = await import(/* webpackIgnore: true */ gwUrl.split(':')[0]);

const body = makeCallDataPublicKeyBody();
return new Promise((resolve, reject) => {
const opts = {
method: 'POST',
headers: {
'content-type': 'application/json',
'content-length': body.length,
},
};
const req = https.request(gwUrl, opts, (res: any) => {
const chunks: Buffer[] = [];
res.on('error', (err: any) => reject(err));
res.on('data', (chunk: any) => chunks.push(chunk));
res.on('end', () => {
resolve(JSON.parse(Buffer.concat(chunks).toString()));
});
});
req.on('error', (err: Error) => reject(err));
req.write(body);
req.end();
});
}

async function fetchRuntimePublicKeyBrowser(
export async function fetchRuntimePublicKeyFromURL(
gwUrl: string,
fetchImpl: typeof fetch,
): Promise<RawCallDataPublicKeyResponse> {
Expand Down Expand Up @@ -124,9 +94,10 @@ export async function fetchRuntimePublicKeyByChainId(
`Unable to fetch runtime public key for network with unknown ID: ${chainId}.`,
);
const fetchImpl = opts?.fetch ?? globalThis?.fetch;
const res = await (fetchImpl
? fetchRuntimePublicKeyBrowser(defaultGateway, fetchImpl)
: fetchRuntimePublicKeyNode(defaultGateway));
if (!fetchImpl) {
throw new Error('No fetch implementation found!');
}
const res = await fetchRuntimePublicKeyFromURL(defaultGateway, fetchImpl);
return toCallDataPublicKey(res.result, chainId);
}

Expand Down Expand Up @@ -201,13 +172,21 @@ export async function fetchRuntimePublicKey(
}

export abstract class AbstractKeyFetcher {
public abstract fetch(upstream: UpstreamProvider): Promise<CallDataPublicKey>;
public abstract fetch(
upstream: UpstreamProvider,
timeoutMilliseconds?: number,
): Promise<CallDataPublicKey>;
public abstract cipher(upstream: UpstreamProvider): Promise<Cipher>;
}

export class KeyFetcher extends AbstractKeyFetcher {
readonly timeoutMilliseconds: number;
public pubkey?: CallDataPublicKey;
#isBackgroundRunning = false;

get isBackgroundRunning(): boolean {
return this.#isBackgroundRunning;
}

constructor(in_timeoutMilliseconds?: number) {
super();
Expand All @@ -223,11 +202,15 @@ export class KeyFetcher extends AbstractKeyFetcher {
* @param upstream Upstream ETH JSON-RPC provider
* @returns calldata public key
*/
public async fetch(upstream: UpstreamProvider): Promise<CallDataPublicKey> {
public async fetch(
upstream: UpstreamProvider,
timeoutMilliseconds?: number,
): Promise<CallDataPublicKey> {
if (this.pubkey) {
const pk = this.pubkey;
const expiry = Date.now() - this.timeoutMilliseconds;
if (pk.fetched && pk.fetched.valueOf() > expiry) {
const expiry =
Date.now() - (timeoutMilliseconds ?? this.timeoutMilliseconds);
if (pk.fetched && pk.fetched.valueOf() >= expiry) {
// XXX: if provider switch chain, may return cached key for wrong chain
return pk;
}
Expand All @@ -239,6 +222,33 @@ export class KeyFetcher extends AbstractKeyFetcher {
const kp = await this.fetch(upstream);
return X25519DeoxysII.ephemeral(kp.key, kp.epoch);
}

public cipherSync() {
if (!this.pubkey) {
throw new Error('No cached pubkey!');
}
const kp = this.pubkey;
return X25519DeoxysII.ephemeral(kp.key, kp.epoch);
}

public stopBackground() {
this.#isBackgroundRunning = false;
}

public async runInBackground(upstream: UpstreamProvider) {
if (this.#isBackgroundRunning) return;

// Reduce wait time by 10%, so it eagerly fetches
const waitMilliseconds =
this.timeoutMilliseconds - this.timeoutMilliseconds / 10;

while (this.#isBackgroundRunning) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of this, not sure how timeoutMilliseconds is set, especially with 10% magic above. But I would do it with setInterval.

await new Promise((resolve) =>
setTimeout(resolve, this.timeoutMilliseconds),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should clear this setTimeout in stopBackground function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I have refactored the timeout logic

);
await this.fetch(upstream, waitMilliseconds);
}
}
}

export class MockKeyFetcher extends AbstractKeyFetcher {
Expand Down
Loading
Loading