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/viem: doctests #419

Merged
merged 7 commits into from
Oct 1, 2024
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
63 changes: 39 additions & 24 deletions integrations/viem-v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,67 @@

A plugin for [Viem] 2.x that encrypts transactions, gas estimations and calls to
the Oasis Sapphire network to enable end-to-end encryption between the dApp and
smart contracts.
smart contracts. It provides three functions which can be used together or
separately, see the guides below for usage instructions:

* `sapphireHttpTransport` - [Transport] that intercepts & encrypts requests.
* `wrapWalletClient` - Wraps a [WalletClient], to provide encryption.
* `createSapphireSerializer` - [Transaction serializer], that encrypts calldata.

[Viem]: https://viem.sh/
[WalletClient]: https://viem.sh/docs/clients/wallet.html
[Transaction serializer]: https://viem.sh/docs/chains/serializers#api
[Transport]: https://viem.sh/docs/clients/transports/http

## Usage

First install the package.
Add the package to your project:

```
npm install @oasisprotocol/sapphire-viem-v2 [email protected]
```

Next you must ensure that any clients use the `sapphireHttpTransport()` to encrypt
any unsigned communication, for example when using [hardhat-viem] pass the
`transport` parameter when constructing a Public Client:
For local testing the chain configuration for `sapphireLocalnet` is available
in the `@oasisprotocol/sapphire-viem-v2` package installed above. The Sapphire
`mainnet` and `testnet` configurations are available in `viem/chains`:

[hardhat-viem]: https://hardhat.org/hardhat-runner/docs/advanced/using-viem
```ts
import { sapphireLocalnet } from '@oasisprotocol/sapphire-viem-v2';
import { sapphire, sapphireTestnet } from 'viem/chains';
```

```typescript
import { sapphireLocalnet, sapphireHttpTransport } from '@oasisprotocol/sapphire-viem-v2';
## Encryption

const publicClient = await hre.viem.getPublicClient({
chain: sapphireLocalnet,
transport: sapphireHttpTransport()
});
```
Use the `sapphireHttpTransport()` transport to automatically intercept and
encrypt the calldata provided to the `eth_estimateGas`, `eth_call` and
`eth_sendTransaction` JSON-RPC calls.

The Sapphire transport will only encrypt transactions if connected to an
in-browser wallet provider which accepts `eth_sendTransaction` calls. To encrypt
transactions when using a local wallet client you must not only provide the
`transport` parameter, but must also wrap the wallet client, as such:
Transaction calldata will be encrypted while using the in-browser wallet
provider (`window.ethereum`) because this uses the `eth_sendTransaction`
JSON-RPC call to sign transactions.

> [!IMPORTANT]
> To encrypt transactions when using a local wallet client you must not only
> provide the `transport` parameter, but must also wrap the wallet client.

This example creates local wallet that is then wrapped with `wrapWalletClient`
which replaces the transaction serializer with one which performs Sapphire
encryption:

```typescript
import { createWalletClient } from 'viem'
import { english, generateMnemonic, mnemonicToAccount } from 'viem/accounts';
import { sapphireLocalnet, sapphireHttpTransport, wrapWalletClient } from '@oasisprotocol/sapphire-viem-v2';

const account = mnemonicToAccount(generateMnemonic(english));

const walletClient = await wrapWalletClient(createWalletClient({
account,
chain: sapphireLocalnet,
transport: sapphireHttpTransport()
}));
```

### Chains

The chain configuration for `sapphireLocalnet` is available in the `@oasisprotocol/sapphire-viem-v2` package as seen above.
The Sapphire `mainnet` and `testnet` configurations are available in `viem/chains`
```ts
import { sapphire, sapphireTestnet } from 'viem/chains';
```
Be careful to verify that any transactions which contain sensitive information
are encrypted by checking the Oasis block explorer and looking for the green
lock icon.
2 changes: 2 additions & 0 deletions integrations/viem-v2/hardhat.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require("@nomicfoundation/hardhat-viem");
module.exports = {};
1 change: 1 addition & 0 deletions integrations/viem-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"devDependencies": {
"@biomejs/biome": "^1.7.0",
"@types/node": "^18.19.31",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"vitest": "^1.6.0"
Expand Down
39 changes: 39 additions & 0 deletions integrations/viem-v2/test/docs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { rimraf } from "rimraf";
import { afterAll, beforeAll, describe, expect, it } from "vitest";

const EXTRACT_CODE_RX = /\`\`\`(?<lang>\w+)\s*(?<content>[\s\S]*?)\`\`\`/gm;

function extractCode(markdown: string) {
return Array.from(markdown.matchAll(EXTRACT_CODE_RX), (_) => {
return {
lang: _.groups?.lang,
content: _.groups?.content,
};
});
}

const TEST_DIR = join(process.cwd(), "test-modules");

describe("README.md", async () => {
beforeAll(async () => {
await mkdir(TEST_DIR, { recursive: true });
});
afterAll(async () => {
await rimraf(TEST_DIR);
});
for (const [index, c] of extractCode(
await readFile("./README.md", "utf8"),
).entries()) {
const content = c.content;
if (content) {
it(`Code snippet #${index + 1}`, async () => {
const fileName = join(TEST_DIR, `example_${index + 1}.ts`);
await writeFile(fileName, content);
console.log(fileName, content);
await expect(import(fileName)).resolves.not.toThrow();
});
}
}
});
Loading
Loading