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 15 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
10 changes: 10 additions & 0 deletions clients/js/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ The format is inspired by [Keep a Changelog].

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

## 1.3.2 (2024-03-24)

### Fixed

- Wagmi v2 & Viem v2 support

### Changed

- Supports only Node v18+

## 1.3.1 (2024-01-26)

### Fixed
Expand Down
1 change: 0 additions & 1 deletion clients/js/package.json
Original file line number Diff line number Diff line change
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
14 changes: 11 additions & 3 deletions clients/js/scripts/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async function getBody(request: IncomingMessage): Promise<string> {
});
}

const LISTEN_PORT = 3000;
const LISTEN_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 +43,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 +163,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();
}
39 changes: 5 additions & 34 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
32 changes: 29 additions & 3 deletions clients/js/src/compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export function wrap<U extends Ethers5Signer>(
options?: SapphireWrapOptions,
): U & SapphireAnnex; // Ethers signers

export function wrap<U extends string>(
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
upstream: U,
options?: SapphireWrapOptions,
): Ethers5Provider & EIP1193Provider & SapphireAnnex; // Gateway URL

export function wrap<U extends UpstreamProvider>(
upstream: U,
options?: SapphireWrapOptions,
Expand Down Expand Up @@ -124,7 +129,7 @@ function isEIP1193Provider(upstream: object): upstream is EIP1193Provider {
return 'request' in upstream;
}

function wrapEIP1193Provider<P extends EIP1193Provider>(
export function wrapEIP1193Provider<P extends EIP1193Provider>(
upstream: P,
options?: SapphireWrapOptions,
): P & SapphireAnnex {
Expand Down Expand Up @@ -156,7 +161,7 @@ function hookEIP1193Request(
return async (args: Web3ReqArgs) => {
const signer = await provider.getSigner();
const cipher = await options.fetcher.cipher(provider);
const { method, params } = await prepareRequest(args, signer, cipher);
const { method, params } = await prepareRequest(args, cipher, signer);
const res = await signer.provider.send(method, params ?? []);
if (method === 'eth_call') {
return await cipher.decryptEncoded(res);
Expand Down Expand Up @@ -247,6 +252,26 @@ export function wrapEthersProvider<P extends Provider | Ethers5Provider>(
estimateGas: hookEthersCall(provider, 'estimateGas', filled_options),
};

// Provide an EIP-1193 compatible endpoint for Ethers providers
if (!('request' in provider) && 'send' in provider) {
Copy link
Contributor

@lubej lubej Apr 8, 2024

Choose a reason for hiding this comment

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

Isn't there some kind of utility function for this already. isEip1193Provider which returns the correct typed provider. If not, please create one.

Copy link
Member Author

Choose a reason for hiding this comment

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

'request' in provider is shorter and more explicit than isEip1193Provider(provider)

I have a refactor waiting in my head that removes this messiness

hooks['request'] = async function (args: {
method: string;
params: any[];
}) {
const cipher = await filled_options.fetcher.cipher(provider);
const { method, params } = await prepareRequest(
args,
cipher,
signer as any,
);
const res = await (provider as any).send(method, params ?? []);
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

The above, will also solve this any.

Copy link
Member Author

Choose a reason for hiding this comment

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

It doesn't solve this any instance, the send interface is exposed by JsonRpcProvider and BrowserProvider, but this could be from ether the Ethers 5 or Ethers 6 packages.

if (method === 'eth_call') {
return await cipher.decryptEncoded(res);
}
return res;
};
}

// When a signer is also provided, we can re-pack transactions
// But only if they've been signed by the same address as the signer
if (signer) {
Expand Down Expand Up @@ -404,8 +429,8 @@ async function callNeedsSigning(

async function prepareRequest(
{ method, params }: Web3ReqArgs,
signer: JsonRpcSigner,
cipher: Cipher,
signer?: JsonRpcSigner,
): Promise<{ method: string; params?: Array<any> }> {
if (!Array.isArray(params)) return { method, params };

Expand All @@ -417,6 +442,7 @@ async function prepareRequest(
}

if (
signer &&
(method === 'eth_call' || method === 'eth_estimateGas') &&
(await callNeedsSigning(params[0]))
) {
Expand Down
4 changes: 4 additions & 0 deletions clients/js/type-only-test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,7 @@ test('EIP-1193', () => {
const provider = sapphire.wrap(window.ethereum);
}
});

test('URL', () => {
const provider = sapphire.wrap('https://sapphire.oasis.io');
});
25 changes: 14 additions & 11 deletions docs/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ out [line 66]. Read [the guide](guide.mdx#contract-logs) to learn more.

:::

[`wagmi`]: https://wagmi.sh/
[`viem`]: https://viem.sh/

## Signing Sapphire Calls and Transactions in Browser

Now, let's explore the frontend of our dApp. Begin by moving into the
Expand All @@ -65,11 +68,11 @@ the following changes:
--- a/hardhat-boilerplate/frontend/src/components/Dapp.js
+++ b/hardhat-boilerplate/frontend/src/components/Dapp.js
@@ -2,6 +2,7 @@

// We'll use ethers to interact with the Ethereum network and our contract
import { ethers } from "ethers";
lubej marked this conversation as resolved.
Show resolved Hide resolved
+import * as sapphire from '@oasisprotocol/sapphire-paratime';

// We import the contract's artifacts and address here, as we are going to be
// using them with ethers
@@ -22,7 +23,7 @@
Expand All @@ -78,16 +81,16 @@ the following changes:
// to use when deploying to other networks.
-const HARDHAT_NETWORK_ID = '1337';
+const HARDHAT_NETWORK_ID = '23295'; // Sapphire Testnet
CedarMist marked this conversation as resolved.
Show resolved Hide resolved

// This is an error code that indicates that the user canceled a transaction
const ERROR_CODE_TX_REJECTED_BY_USER = 4001;
@@ -225,14 +226,20 @@

async _initializeEthers() {
// We first initialize ethers by creating a provider using window.ethereum
- this._provider = new ethers.providers.Web3Provider(window.ethereum);
+ this._provider = sapphire.wrap(new ethers.providers.Web3Provider(window.ethereum));

- // Then, we initialize the contract using that provider and the token's
- // artifact. You can do this same thing with your contracts.
+ // Then, we initialize two contract instances:
Expand All @@ -105,26 +108,26 @@ the following changes:
+ this._provider.getSigner()
);
}

@@ -294,7 +301,7 @@

// We send the transaction, and save its hash in the Dapp's state. This
// way we can indicate that we are waiting for it to be mined.
- const tx = await this._token.transfer(to, amount);
+ const tx = await this._tokenWrite.transfer(to, amount);
this.setState({ txBeingSent: tx.hash });

// We use .wait() to wait for the transaction to be mined. This method
@@ -360,8 +367,8 @@
return true;
}
- this.setState({

- this.setState({
- networkError: 'Please connect Metamask to Localhost:8545'
+ this.setState({
+ networkError: 'Please connect to Sapphire ParaTime Testnet'
});

return false;
```

Expand Down
9 changes: 4 additions & 5 deletions docs/guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ but it's made simple by our compatibility library, [@oasisprotocol/sapphire-para

There are compatibility layers in other languages, which may be found in [the repo].


[@oasisprotocol/sapphire-paratime]: https://www.npmjs.com/package/@oasisprotocol/sapphire-paratime
[the repo]: https://github.com/oasisprotocol/sapphire-paratime/tree/main/clients

Expand All @@ -113,15 +112,15 @@ There are compatibility layers in other languages, which may be found in [the re
### Wallets

Sapphire is compatible with popular self-custodial wallets including MetaMask,
Ledger, Brave, and so forth. You can also use libraries like Web3.js and Ethers
Ledger, Brave, and so forth. You can also use libraries like Wagmi and Ethers
to create programmatic wallets. In general, if it generates secp256k1 signatures,
it'll work just fine.

### Languages & Frameworks

Sapphire is programmable using any language that targets the EVM, such as Solidity
or Vyper. If you prefer to use an Ethereum framework like Hardhat or Foundry, you
can also use those with Sapphire; all you need to do is set your Web3 gateway URL.
Sapphire is programmable using any language that targets the EVM, such as Solidity,
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
Fe or Vyper. If you prefer to use an Ethereum framework like Hardhat or Foundry,
you can also use those with Sapphire; all you need to do is set your Web3 gateway URL.
You can find the details of the Oasis Sapphire Web3 endpoints
[here](https://github.com/oasisprotocol/docs/blob/main/docs/dapp/sapphire/README.mdx#rpc-endpoints).

Expand Down
26 changes: 26 additions & 0 deletions examples/wagmi-v2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

stats.html

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
1 change: 1 addition & 0 deletions examples/wagmi-v2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a [Vite](https://vitejs.dev) project bootstrapped with [`create-wagmi`](https://github.com/wevm/wagmi/tree/main/packages/create-wagmi).
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe update this README here? To something like Wagmi example, or just delete the README.

12 changes: 12 additions & 0 deletions examples/wagmi-v2/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Create Wagmi</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions examples/wagmi-v2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "example-wagmi",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "biome check .",
"format": "biome format --write .",
"preview": "vite preview",
"start:server": "vite --port 3000"
},
"dependencies": {
"@oasisprotocol/sapphire-wagmi-v2": "workspace:^",
"@tanstack/react-query": "5.0.5",
"abitype": "^1.0.2",
"react": ">=18",
"react-dom": ">=18",
"viem": "2.x",
"wagmi": "2.x"
},
"devDependencies": {
"@biomejs/biome": "^1.6.1",
"@types/react": "^18.2.67",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
"buffer": "^6.0.3",
"rollup-plugin-visualizer": "^5.12.0",
"typescript": "^5.4.2",
"vite": "^4.5.2"
}
}
Loading
Loading