-
Notifications
You must be signed in to change notification settings - Fork 36
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
EIP-0012 - dApp-Wallet Web Bridge #23
Draft
rooooooooob
wants to merge
6
commits into
ergoplatform:master
Choose a base branch
from
rooooooooob:eip-0012
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
720b188
Initial draft of EIP-0012
rooooooooob c745a55
responding to comments
rooooooooob a47bce0
Update EIP-0012 based on minor changes thought of during initial impl…
rooooooooob d0d5d9a
Update Tx (unsigned)
rooooooooob 3feb014
Include full box information in UnsignedInput
rooooooooob b70f406
Update eip-0012.md
rooooooooob File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,348 @@ | ||||||
# EIP-0012: dApp-Wallet Web Bridge | ||||||
|
||||||
* Authors: rooooooooob, Robert Kornacki | ||||||
* Created: 10-Aug-2020 | ||||||
* Modified: 12-Mar-2021 | ||||||
* License: CC0 | ||||||
* Forking: not needed | ||||||
|
||||||
## Description | ||||||
|
||||||
This document describes a communication bridge between cryptocurrency wallets and Ergo dApps which interact via javascript code injected into a web context. The communication is initiated from the dApp to the wallet. | ||||||
|
||||||
## Motivation | ||||||
|
||||||
Distributed apps (dApps) often require access to a user's wallet in order to function, and this is typically from a web context. In other cryptocurrencies, such as Ethereum, this is done via a library such as web3 that injects code into a web browser that the dApp can interact with to create transactions or obtain address data using a user's wallet. This should be done in a manner that maximizes privacy and provides an easy user experience. | ||||||
|
||||||
## API | ||||||
|
||||||
The proposed API is limited to just the minimum wallet <-> dApp communication needed rather than providing lots of utility functions (tx building, data conversions, etc) or node-access (for UTXO scanning). This is different compared to web3 as that functionality could be modular in a separate library and Ergo smart contracts don't need as much direct node access for basic dApp functionality compared to Ethereum. | ||||||
|
||||||
This API is accessible from a javascript object `ergo` that is injected into the web context upon wallet consent. Before this just the free `ergo_request_read_access()` and `ergo_check_read_access()` functions are injected into the web context. An event handler can also be registered to the web context to detect wallet disconnects tagged as `"ergo_wallet_disconnected"`. | ||||||
|
||||||
The following definitions are provided using flow type annotations to describe the underlying javascript API / JSON data types. | ||||||
|
||||||
### ergo_request_read_access(): Promise\<bool> | ||||||
|
||||||
Errors: APIError | ||||||
|
||||||
If the API was not already injected, requests access to the API, and if the user consents, injects the full API similar to EIP-1102 via an `ergo` object. Returns true if the API has been injected, or false if the user refused access. | ||||||
Does nothing and returns true if the API was already injected. | ||||||
|
||||||
The wallet can choose to maintain its own whitelist if desired but this is on the wallet side and not a part of this standard. It could possibly also expose a subset of its state as responses to the other calls but this is also out of scope. All following API methods are injected upon consent. | ||||||
|
||||||
### ergo_check_read_access(): Promise\<bool> | ||||||
|
||||||
Errors: APIError | ||||||
|
||||||
Returns true if the full API was injected and we have read access, and false otherwise. This is potentially useful to have a cleaner UX on the dApp side (e.g. display request read access button for optional functionality rather than always requesting it). | ||||||
|
||||||
### ergo.get_utxos(amount: Value = undefined, token_id: String = 'ERG', paginate: Paginate = 'undefined'): Promise\<Box[] | undefined> | ||||||
|
||||||
Errors: APIError, PaginateError | ||||||
|
||||||
Returns a list of all unspent boxes tracked by the wallet that it is capable of signing for. | ||||||
Takes an optional {amount} parameter, if not `undefined`, limits the returned UTXOs to {amount} of the token {token_id} to create it. if {amount} is not `undefined` and there is not sufficient balance of the specified token to reach {amount} then `undefined` is returned. | ||||||
If {paginate} is not `undefined` then results are returned in a paginated manner in chronological order of use on the blockchain. If {paginate} is out of range, `PaginateError` is returned. | ||||||
|
||||||
### ergo.get_balance(token_id: String = 'ERG'): Promise\<Value> | ||||||
|
||||||
Errors: APIError | ||||||
|
||||||
Returns available balance of {token_id} owned by the wallet. A useful and minimal convenience functionality, that while could be served by iterating the return of `get_utxos` and summing, is useful enough and could be most likely quickly returned by wallets without needing to do this calculation. | ||||||
|
||||||
### ergo.get_used_addresses(paginate: Paginate = 'undefined'): Promise\<Address[]> | ||||||
|
||||||
Errors: APIError, PaginateError | ||||||
|
||||||
Returns all used (exist in confirmed transactions) addresses of the user's wallet. | ||||||
If {paginate} is not `undefined` then results are returned in a paginated manner in chronological order of use on the blockchain. If {paginate} is out of range, `PaginateError` is returned. | ||||||
|
||||||
### ergo.get_unused_addresses(): Promise\<Address[]> | ||||||
|
||||||
Errors: APIError | ||||||
|
||||||
Returns unused addresses available that have not been used yet. The wallet would need to pre-generate addresses here within its restrictions (e.g. discover gap on indices for HD wallets). | ||||||
|
||||||
### ergo.get_change_address(): Promise\<Address> | ||||||
|
||||||
Errors: APIError | ||||||
|
||||||
Returns an address owned by the wallet that should be used as a change address to return leftover assets during transaction creation back to the connnected wallet. This can be used as a generic receive address as well. | ||||||
|
||||||
### ergo.sign_tx(tx: Tx) -> Promise\<SignedTx> | ||||||
|
||||||
Errors: APIError, TxSignError | ||||||
|
||||||
A signed tx is returned if the wallet can provide proofs for all inputs (P2PK ones). If not it produces an error. This should also have some kind of user pop-up which if rejected would produce an error as well. This way signing/keys are kept entirely in the wallet. | ||||||
|
||||||
### ergo.sign_tx_input(tx: Tx, index: number): Promise\<SignedInput> | ||||||
|
||||||
Errors: APIError, TxSignError | ||||||
|
||||||
Lower level tx signing functionality that signs a single input if the wallet is able to, and returns an error if not. This can be useful for constructing transactions consisting of inputs from multiple parties, and should be general enough for future transaction signing use-cases. Likewise with `sign_tx` this should ask for user consent and can be rejected producing an error. | ||||||
|
||||||
### ergo.sign_data(addr: Address, message: String): Promise\<String> | ||||||
|
||||||
Errors: APIError, DataSignError | ||||||
|
||||||
Signs the generic data {data} encoded as a hex string using the private key associated with {addr} if possible. If the wallet does not own this address, an error is returned. This should ask for user consent and produce an error if rejected. The wallet should also implement a message signing protocol such as the proposed [EmIP-005](https://github.com/Emurgo/EmIPs/blob/5b00fce84f31eb763892186eb9c88739ec809333/specs/emip-005.md) in order to make this endpoint safer for use, and make it harder for the user to accidentally sign data such as transactions, blocks, etc. Returns the signed data as a hex-encoded string. | ||||||
|
||||||
TBD: Hash algorithm/format to use. | ||||||
|
||||||
### ergo.submit_tx(tx: SignedTx): Promise\<TxId> | ||||||
|
||||||
Errors: APIError, TxSendError | ||||||
|
||||||
Uses the wallet to send the given tx and returns the TxId for the dApp to track when/if it is included as its own responsibility. This is technically not mandatory but any wallet that is using such a bridge should have this functionality. Wallets can additionally choose to rate-limit this call, and can return an error if they did not or could not send the transaction. | ||||||
|
||||||
|
||||||
## Data Formats | ||||||
|
||||||
The initial design for most of these types are meant to be the same as the JSON types in the Ergo full node, which matches up as of now with the `sigma-rust` representations, but these definitions are the definitive types. | ||||||
|
||||||
### Address | ||||||
|
||||||
String as the standard base58 encoding of an address. | ||||||
|
||||||
### Box | ||||||
|
||||||
Transaction output box. Format is the same as `sigma-rust`: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no |
||||||
``` | ||||||
type Box = {| | ||||||
boxId: BoxId, | ||||||
value: Value, | ||||||
ergoTree: ErgoTree, | ||||||
assets: TokenAmount[], | ||||||
additionalRegisters: {| [string]: Constant |}, | ||||||
creationHeight: number, | ||||||
transactionId: TxId, | ||||||
index: number, | ||||||
|}; | ||||||
``` | ||||||
Additional registers have string ids "R4", "R5" to "R9". | ||||||
|
||||||
### BoxCandidate | ||||||
|
||||||
A candidate (no TX/box IDs) for an output in an `UnsignedTransaction` | ||||||
``` | ||||||
type Box = {| | ||||||
rooooooooob marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
value: Value, | ||||||
ergoTree: ErgoTree, | ||||||
assets: TokenAmount[], | ||||||
additionalRegisters: {| [string]: Constant |}, | ||||||
creationHeight: number, | ||||||
|}; | ||||||
``` | ||||||
Additional registers have string ids "R4", "R5" to "R9". | ||||||
|
||||||
### BoxId | ||||||
|
||||||
Hex string of the box id. | ||||||
|
||||||
### Constant | ||||||
|
||||||
Uses `sigma-rust` rep - Hex-encoded bytes for `SigmaSerialize` of a constant. | ||||||
|
||||||
### ContextExtension | ||||||
|
||||||
Uses the full node JSON rep (id to hex-encoded Sigma-state value). Empty object is for P2PK. | ||||||
``` | ||||||
type ContextExtension = {||} | {| | ||||||
values: {| [string]: string |} | ||||||
|}; | ||||||
``` | ||||||
or the empty `{}` object for P2PK inputs. | ||||||
|
||||||
### ErgoTree | ||||||
|
||||||
Uses `sigma-rust` rep - Hex string representing serialized bytes of the tree using `SigmaSerialize` | ||||||
|
||||||
### DataInput | ||||||
|
||||||
Read-only input (e.g. oracle usage) that is not spendable. Uses `sigma-rust` JSON rep: | ||||||
``` | ||||||
type DataInput = {| | ||||||
boxId: BoxId, | ||||||
|}; | ||||||
``` | ||||||
|
||||||
### SignedInput | ||||||
|
||||||
A signed `UnsignedInput`. Uses full-node JSON rep: | ||||||
``` | ||||||
type SignedInput = {| | ||||||
boxId: BoxId, | ||||||
spendingProof: ProverResult, | ||||||
|}; | ||||||
``` | ||||||
|
||||||
### UnsignedInput | ||||||
|
||||||
This is the same format as `Box` but with an `extension` field as well. | ||||||
``` | ||||||
type UnsignedInput = {| | ||||||
extension: ContextExtension, | ||||||
boxId: BoxId, | ||||||
value: Value, | ||||||
ergoTree: ErgoTree, | ||||||
assets: TokenAmount[], | ||||||
additionalRegisters: {| [string]: Constant |}, | ||||||
creationHeight: number, | ||||||
transactionId: TxId, | ||||||
index: number, | ||||||
|}; | ||||||
rooooooooob marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
``` | ||||||
|
||||||
Including the entire details of the Box instead of just its id was chosen to facilitate the signing of transactions that the user's wallet does not or can not track. There are two primary cases where this happens: | ||||||
* The input is P2S and the wallet might not be tracking those as they can be user-agnostic | ||||||
* The input was a recently created output that might not be accepted by the wallet's backend yet, as would be the case if the dApp just created it. | ||||||
|
||||||
## Paginate | ||||||
|
||||||
``` | ||||||
type Paginate = {| | ||||||
page: number, | ||||||
limit: number, | ||||||
|}; | ||||||
``` | ||||||
Used to specify optional pagination for some API calls. Limits results to {limit} each page, and uses a 0-indexing {page} to refer to which of those pages of {limit} items each. | ||||||
|
||||||
### ProverResult | ||||||
Uses `sigma-rust` rep - proof is a byte array serialized using `SigmaSerialize` in hex format: | ||||||
``` | ||||||
type ProverResult = {| | ||||||
proofBytes: string, | ||||||
extension: ContextExtension, | ||||||
|}; | ||||||
``` | ||||||
|
||||||
### SignedTx | ||||||
|
||||||
Uses `sigma-rust` rep for a transaction: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
``` | ||||||
type SignedTx = {| | ||||||
id: TxId, | ||||||
inputs: SignedInput[], | ||||||
dataInputs: DataInput[], | ||||||
outputs: Box[], | ||||||
size: number, | ||||||
|}; | ||||||
``` | ||||||
with the difference with `Tx` is that the inputs are signed (`SignedInput` instead of `UnsignedInput`) | ||||||
|
||||||
|
||||||
### TokenAmount | ||||||
|
||||||
Uses `sigma-rust` rep: | ||||||
``` | ||||||
type TokenAmount = {| | ||||||
tokenId: TokenId, | ||||||
amount: Value, | ||||||
|}; | ||||||
``` | ||||||
|
||||||
### TokenId | ||||||
|
||||||
Uses `sigma-rust` rep - Hex string of the id. | ||||||
|
||||||
### Tx | ||||||
|
||||||
An unsigned transaction. Uses a modified version of the transaction representation in `sigma-rust`. | ||||||
``` | ||||||
type Tx = {| | ||||||
inputs: UnsignedInput[], | ||||||
dataInputs: DataInput[], | ||||||
outputs: BoxCandidate[], | ||||||
|}; | ||||||
``` | ||||||
This differs from `Tx` in that that `inputs` would be `[UnsignedInput]`, there is no ID, and the boxes are just candidates. | ||||||
|
||||||
### TxId | ||||||
|
||||||
Same as `sigma-rust` - string hex encoding | ||||||
|
||||||
### Value | ||||||
|
||||||
BigNum-like object. Represents a value which may or may not be ERG. Value is in the smallest possible unit (e.g. nanoErg for ERG). It can be either a `number` or a `string` using standard unsigned integer text representation. | ||||||
|
||||||
### TxSendError | ||||||
|
||||||
``` | ||||||
TxSendErrorCode { | ||||||
Refused: 1, | ||||||
Failure: 2, | ||||||
} | ||||||
TxSendError { | ||||||
code: TxSendErrorCode, | ||||||
info: String | ||||||
} | ||||||
``` | ||||||
|
||||||
* Refused - Wallet refuses to send the tx (could be rate limiting) | ||||||
* Failure - Wallet could not send the tx | ||||||
|
||||||
### TxSignError | ||||||
|
||||||
``` | ||||||
TxSignErrorCode { | ||||||
ProofGeneration: 1, | ||||||
UserDeclined: 2, | ||||||
} | ||||||
TxSignError { | ||||||
code: TxSignErrorCode, | ||||||
info: String | ||||||
} | ||||||
``` | ||||||
|
||||||
* ProofGeneration - User has accepted the transaction sign, but the wallet was unable to sign the transaction (e.g. not having some of the private keys) | ||||||
* UserDeclined - User declined to sign the transaction | ||||||
|
||||||
|
||||||
### DataSignError | ||||||
|
||||||
``` | ||||||
DataSignErrorCode { | ||||||
ProofGeneration: 1, | ||||||
AddressNotPK: 2, | ||||||
UserDeclined: 3, | ||||||
InvalidFormat: 4, | ||||||
} | ||||||
DataSignError { | ||||||
code: DataSignErrorCode, | ||||||
info: String | ||||||
} | ||||||
``` | ||||||
|
||||||
* ProofGeneration - Wallet could not sign the data (e.g. does not have the secret key associated with the address) | ||||||
* AddressNotPK - Address was not a P2PK address and thus had no SK associated with it. | ||||||
* UserDeclined - User declined to sign the data | ||||||
* InvalidFormat - If a wallet enforces data format requirements, this error signifies that the data did not conform to valid formats. | ||||||
|
||||||
### APIError | ||||||
|
||||||
``` | ||||||
APIErrorCode { | ||||||
InvalidRequest: -1, | ||||||
InternalError: -2, | ||||||
Refused: -3, | ||||||
} | ||||||
APIError { | ||||||
code: APIErrorCode, | ||||||
info: string | ||||||
} | ||||||
``` | ||||||
|
||||||
* InvalidRequest - Inputs do not conform to this spec or are otherwise invalid. | ||||||
* InternalError - An error occurred during execution of this API call. | ||||||
* Refused - The request was refused due to lack of access - e.g. wallet disconnects. | ||||||
|
||||||
## PaginateError | ||||||
|
||||||
``` | ||||||
type PaginateError = {| | ||||||
maxSize: number, | ||||||
|}; | ||||||
``` | ||||||
{maxSize} is the maximum size for pagination and if we try to request pages outside of this boundary this error is thrown. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to avoid conflicts with multiple wallets, can we do a slight change on the access API? Maybe something like
ergo.{walletName}.requestReadAccess()
andergo.{walletName}.checkReadAccess()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's what we ended up doing for CIP-0030 for Cardano which I based off of this. I'm not sure if it's fine to do such a breaking change at this point since this has been up for a long time although it's still a draft PR. I'll wait and see what the Ergo devs think about this. We also don't have any functionality for versioning either. I mentioned both of these in my comment above in August. There are additional events that can be of use for dApps as well that could be added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with @capt-nemo429 . Explicitly distinguishing wallets in the dApp connector API is a crucial feature. Shouldn't be too problematic to migrate.