Closed grod220 closed 3 months ago
Hey @turbocrime and @grod220, wanted to discuss the API of the client library and touch both #1093, #825, #340 and #341 – all issues describe the ways to connect to the extensions and consume data from the blockchain. I read the related issues and did my research with a goal to decompose the issue. So, the following read aims to solve this user pain:
I, as a user of Penumbra, want to connect my dApp to my third-party wallet and perform transactions and query the blockchain.
It does not explain how users should create their wallets. I'll assume their wallets are Prax's twins.
I am highly inspired by the Viem library for Ethereum. In my opinion, it has one of the best DX in the field by having a notion of clients:
import { createPublicClient, createWalletClient, http, custom } from 'viem';
import { mainnet } from 'viem/chains';
// Public client – provides the access to RPC for Ethereum blockchain
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
});
const balance = await publicClient.getBalance({
address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
});
// Wallet client – allows sending transactions on behalf on the connected user
const client = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum!)
});
const hash = await client.sendTransaction({
account: address,
to: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
value: parseEther('0.001')
})
I am sure we could make connection to Penumbra and its wallets similar. The only difference is that Penumbra won't have a Public client – all actions and even queries must be made by the user connected to wallet.
Here are the main concepts that I think we need for the comfortable dApp development by third-parties:
window[Symbol('penumbra')]
but this creates race conditions between wallets. The EIP-6963 would solve this problem.ViewService
, StakeService
, DexService
, etc., will be unified into one interface within this API. A field supportedServices
should be added to the wallet configuration, so it would instantly throw errors in case of any non-supported apis.With this in mind, I propose the following API:
import { createClient } from '@penumbra-zone/client';
// Calling `createClient` starts listening to "eip6963:announceProvider" event
// but we should rename it to not confuse with Ethereum
const client = createClient();
// Returns the list of discovered wallets. Probably a function `onWalletDiscovered` should also be
// created to subscribe to new wallets sending events – this way users won't have to reload the page to see them
const wallets = client.getInjectedWallets();
// Connect to any of discovered injected wallets
// Additionally, `client.onConnectionChange` could be called to monitor for the connection
const wallet = client.connect(wallets[0]);
// Consume the `ViewService`
const { address } = await client.view.addressByIndex({});
I believe such API would enhance Penumbra adoption and bring many projects connected to Penumbra wallets and even blockchain newcomers could easily create their first app in the private blockchain!
What do you think about this API? Maybe I misunderstood some theory behind the wallets or it is just irrelevant – please hit me with your opinion!
glancing over this. interesting it was published around last year when i was initially working on the wallet injection, would have been cool to read then!
some of the conflict conditions described in the article don't exist in our implementation and ive prototyped another design https://github.com/penumbra-zone/web/pull/1145 with some other benefits/considerations
i think specifically adopting an eth standard is not something that works. penumbra is expected to integrate more with the cosmos ecosystem
will look at this more soon but i think significant changes have been declined by @grod220 as not a current priority
the transport is capable of type-agnostic transmission but currently the developer is expected to init with the message types they expect to handle, and connect to an extension that is aware of those types
Helpful writeup @VanishMax. Interesting to see references in the industry for a similar problem space. Timing-wise, for now it's best to be forward-compatible (ish) to the current injected provider interface. However, it may be good to capture this in another issue so it doesn't get lost.
I'd say the main effort of this issue is to ensure consumers are able to create reactive interfaces. At the moment, dapps cannot for example create a "Connect" button that listens to the current state of the wallet. It has to re-request/poll to get the latest state.
In the client package we expose methods that frontends will use to connect to our wallet. After working with the cosmos-kit, think we should consider some enhancements.
cosmos-kit library
Cosmology's cosmos-kit is a standard in cosmos for connecting to wallets. They have a library managing the core logic and packages (such as @cosmos-kit/react) that provide helpers for various frontend frameworks. For our case, we are using the react package to access react hooks such as useWallet which gives us access to the user's wallet state:
The hook is reactive. So whenever the user's wallet state changes, the UI automatically reflects the correct state.
Their API that they expose in WalletContext is helpful to review as well. It has wallet and chain state:
Wallet connection status:
and actions the user can take
API change recommendations for us
WalletContext
interface that exposes a wallet state field akin to cosmos-kit plus the functions from the interface above.usePenumbraWallet()
hook that makes use of this new possibility for reactivity