Hi @tomaka,
Thank you for reviewing the draft. I appreciate your feedback.
I intended to update the interfaces I mentioned earlier, but I seem to have lost editing access
. Nevertheless, we’ve tested the interface and identified some improvements. Below is the refined interface proposal:
type Callback<T> = (value: T) => void
type UnsubscribeFn = () => void
interface PolkadotProvider {
// Retrieves the current list of available Chains
// that the dApp can connect to
getChains: () => Chains
// Registers a callback invoked when the list
// of available chains changes
onChainsChange: (chains: Callback<Chains>) => UnsubscribeFn
// Allows the dApp to request the Provider to register a Chain
addChain: (chainspec: string) => Promise<Chain>
}
// The key is the genesis-hash of the chain
type Chains = Record<string, Chain>
interface Chain {
genesisHash: string
name: string
// it pulls the current list of available accounts for this Chain
getAccounts: () => Array<Account>
// registers a callback that will be invoked whenever the list
// of available accounts for this chain has changed
onAccountsChange: (accounts: Callback<Array<Account>>) => UnsubscribeFn
// returns a JSON RPC Provider that it's compliant with new
// JSON-RPC API spec:
// https://paritytech.github.io/json-rpc-interface-spec/api.html
connect: (
// the listener callback that the JsonRpcProvider
// will be sending messages to
onMessage: Callback<string>,
// the listener that will be notified when the connectivity changes
onStatusChange: Callback<ProviderStatus>,
) => Promise<JsonRpcProvider>
}
type ProviderStatus = "connected" | "disconnected"
interface JsonRpcProvider {
// it sends messages to the JSON RPC Server
send: (message: string) => void
// `publicKey` is the SS58Formated public key
// `callData` is the scale encoded call-data
// (module index, call index and args)
createTx: (publicKey: string, callData: Uint8Array) => Promise<Uint8Array>
// it disconnects from the JSON RPC Server and it de-registers
// the `onMessage` and `onStatusChange` callbacks that
// were previously registered
disconnect: UnsubscribeFn
}
interface Account {
// SS58Formated public key
publicKey: string
// The provider may have captured a display name
displayName?: string
}
Let me address your points:
-
On the use of
CallbackandUnsubscribeFntypes:- You’re right regarding
send. It indeed isn’t a callback function, and this oversight has been rectified in the latest review. - For
onMessageandonStateChange: They are genuine callback functions. They’re designed for the consumer to supply, allowing the producer to send data back, aligning with the classic callback definition. - As for
disconnect: The intention behind naming it an “unsubscription function” is to reflect its purpose, which is to de-register theonMessageandonStatusChangecallbacks that were previously set up.
- You’re right regarding
-
Chain Identification with Genesis Hash:
-
I must admit, this was a revelation! When seeking advice from some seasoned experts at Parity on how best to identify a chain (similar to Ethereum’s chain_id and its use by the Ethereum Provider as specified here), I understood that the genesis hash could be employed for such identification. Clearly, I may have misinterpreted this. I appreciate you pointing this out. It’s crucial because it might have inadvertently influenced some API decisions in another library. We’ll certainly need to reevaluate how we can uniquely identify a chain.
-
EDIT: Upon some consultation with domain experts, it appears that the most accurate method to uniquely identify a chain is by utilizing a combination of
(hash_of_forked_block, block_number_of_forked_block). Meaning that: for chains that haven’t experienced any forks, the identifier would be(genesis_hash, 0). We could represent this information as a hexadecimal string, where a non-forked chain will have a 32-byte long hexadecimal string representing the genesis hash. However, for a forked-chain, its identifier will be longer than 32 bytes. This extra length will be attributable to the compact encoded block number, appended to the hash of the forked block. This mechanism ensures that every chain gets a distinct identifier. This approach not only provides a unique identifier for each chain but also allows for easy differentiation between original and forked chains based on the length of the hexadecimal string. What do you think?
-
-
Merging
ChainProviderandJsonRpcProvider:- EDIT: they’ve now been merged. Thanks @tomaka!
-
Account Object Invalidation:
- You’ve touched upon an important change we’ve recently integrated. The suggestion to shift
createTxfromAccounttoChainProviderand specify the account as an additional parameter resonated with us. This not only makesAccounta pure data structure but also alleviates the ambiguity you pointed out. The latest proposal encapsulates this change. Please take a moment to review it and let me know if this clears things up.
- You’ve touched upon an important change we’ve recently integrated. The suggestion to shift
-
Concept of “Suggesting a Chain”:
- The idea behind “suggesting a chain” primarily serves to shield users from potentially harmful dApps. To illustrate: imagine a versatile dApp built for both Kusama and Polkadot. The dApp can allow users to select their preferred network. When a user opts for Polkadot, the dApp checks the availability of the Polkadot relay chain (which is likely present) and then the collectives parachain. If the latter is absent, it prompts the Provider to add it.
- It’s crucial to note that proposing a new chain doesn’t automatically mean the Provider will save or distribute that chain to other dApps. User consent is paramount. If the Provider is, for instance, an extension, it would ideally solicit user approval before connecting to a new chain. Thus, users hold the final decision on chain persistence and sharing across dApps.
- As a hypothetical example, many dApp users (myself included) would be inclined to persist the Polkadot collectives parachain in their provider. Yet, it’s entirely feasible for a Provider to neither retain nor share any user-added chains with other dApps.
-
Regarding
ProviderStatus:- Your interpretation is understandable. The
ProviderStatuspertains not to the state of the light client but to the communication medium enabling the JsonRpcProvider. Examples could range from a WebSocket connection being terminated or a Worker process being unexpectedly halted. - A
connectedstatus implies the JsonRpc API interface is primed for messaging. Conversely, adisconnectedstatus signals that:
a) No mechanism is attending to the messages dispatched viasend.
b) TheonMessagecallback will remain inactive. - Essentially, it notifies the consumer about the unavailability of the medium and perhaps prompts them to initiate a fresh connection. Also, a ‘disconnected’ state is irreversible. I do recognize the importance of clarity in the spec on this, and maybe designations like
ready/haltedmight be more intuitive.
- Your interpretation is understandable. The
-
Promise Error Specifications:
- Absolutely! This oversight will be addressed. Comprehensive documentation detailing potential errors is forthcoming. Thank you for flagging it.
-
Advocating for JSON-RPC Calls:
- While I acknowledge the consistency and predictability that JSON-RPC calls offer, especially when handling known errors, could you elucidate on the additional corner cases these calls could potentially rectify?
-
Minimizing Promises:
- I concur with your perspective on the complexity introduced by promises, especially when considering race conditions. In response, only two functions now return promises, notably
connectandcreateTx, given their inherent asynchronous operations. The polling of accounts and networks has been restructured to be “synchronous”, eliminating the need for promises there.
- I concur with your perspective on the complexity introduced by promises, especially when considering race conditions. In response, only two functions now return promises, notably
Your keen scrutiny has been pivotal in refining this interface. I’m eager to hear your thoughts on these clarifications.