-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
dimitry
committed
Oct 25, 2022
1 parent
e327a66
commit b45154f
Showing
2 changed files
with
569 additions
and
0 deletions.
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,393 @@ | ||
CHIP Number | 0002 | ||
:-------------|:---- | ||
Title | dApp protocol | ||
Description | This proposal describes a Web3 bridge between the browser wallets and dApps on the Chia Network. | ||
Author | [Dimitry Suen](https://github.com/dimitrysuen) | ||
Comments-URI | https://github.com/Chia-Network/chips/pull/9 | ||
Status | Final | ||
Category | Process | ||
Sub-Category | Procedural | ||
Created | 2022-04-19 | ||
Requires | None | ||
Replaces | None | ||
Superseded-By | None | ||
|
||
## Abstract | ||
|
||
This proposal describes a Web3 bridge between the browser wallets and dApps on the Chia Network. | ||
|
||
## Motivation | ||
|
||
More and more decentralized apps are emerging on the Chia Network. As an important entrance to Web3, a user-friendly plug-in browser wallet makes it easier for users to interact on the Chia Network. Meanwhile, dApps require access to call the user's wallet, typically from a web context. | ||
|
||
We propose this dApp protocol for discussion with developers to improve. We hope that Chia Network's browser plug-in wallets will follow this standard to simplify the integration development of dApps. | ||
|
||
## Backwards Compatibility | ||
|
||
The parameters are currently passed via `JSON`, which can be modified for subsequent expansion. | ||
|
||
## Rationale | ||
|
||
To keep the protocol simple and flexible, we do not encapsulate the offer-related and transfer methods, but provide an underlying method called `signCoinSpends`. With a mature javascript/typescript library, the integration experience of wallets for dApp devs would be easy, e.g., the library can provide more features like type hint and change wallet provider. | ||
|
||
## Goal | ||
|
||
We manage our users' coins and private keys while maintaining the security and privacy of their funds. Our primary principle is to protect the assets' security and privacy and maintain compatibility with Chia Wallet as much as possible. | ||
|
||
## Requirements | ||
|
||
None. | ||
|
||
## Specification | ||
|
||
All methods can be called via `window.chia`, such as | ||
|
||
```tsx | ||
interface RequestArguments { | ||
method: string; | ||
params?: object; | ||
} | ||
|
||
interface Wallet { | ||
name: string; // the wallet name | ||
version: string; // the wallet version | ||
apiVersion: string; // the API version adopted by the wallet | ||
request(RequestArguments): Promise<any>; | ||
on(event: string, params?: any): void; | ||
} | ||
|
||
interface ChiaWindow extends Window { | ||
chia?: Wallet | ||
} | ||
|
||
window.chia.request(args: RequestArguments): Promise<any>; | ||
``` | ||
|
||
The dApps and wallet use `browser.tabs.sendMessage` to communicate, the `request` function is a wrapper for the `sendMessage` function. Some methods may need users' mannual approvals. | ||
|
||
This proposal aims to specify an underlying API that third-party libraries can wrap like "wallet.getPublicKeys()" so that they can provide additional features, such as type hint. | ||
|
||
The wallet will throw `MethodNotFoundError` if an undefined method is requested. Considering the scenario of multiple wallets, the wallet should provide `window.yourWalletname` as an entry and setting `windows.chia` as an alias only when `window.chia` is unavailable. | ||
|
||
## Version | ||
|
||
The apiVersion defined by this CHiP is `1.0.0`. | ||
|
||
## Methods | ||
|
||
### chainId | ||
|
||
Return the current chainID. | ||
|
||
| CHAIN NAME | CHAINID | | ||
|----------------|-----------| | ||
| Chia Mainnet | mainnet | | ||
| Chia Testnet10 | testnet10 | | ||
|
||
```tsx | ||
chainId(): string | ||
``` | ||
|
||
### connect | ||
|
||
The dApp requests users' permission to connect the wallet. If the dApp has been approved, the API will return `true`. If the dApp hasn't been approved, the wallet need the user to approve mannually. If the user rejects the request, the API will throw `UserRejectedRequestError`. | ||
|
||
If the dApp has the permission, the dApp can read the wallet information via calling related methods. However, if the user revokes the permission, the dApp needs the user's approval again. | ||
|
||
Parameters as described below. | ||
| Parameter | Description | | ||
|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| eager | If the value is `true`, the wallet will not ask the user to approve and returns `true` if the dApp has permission. However, if the dApp is not permitted, the wallet will throw `UnauthorizedError` . The default value is `false`. | | ||
|
||
```tsx | ||
connect(params: {eager?: boolean}): boolean | ||
``` | ||
|
||
### walletSwitchChain | ||
|
||
The dApp requests to switch to another chain. | ||
|
||
```tsx | ||
walletSwitchChain(params: {chainId: string}): void | ||
``` | ||
|
||
### getPublicKeys | ||
|
||
API returns the public keys managed by the wallet. The wallet can limit the total number of managed public keys to return. | ||
|
||
The wallet can manage many public keys internally but only return 2-3 public keys for privacy consideration. | ||
|
||
Parameters as described below. | ||
| Parameter | Description | | ||
|-----------|----------------------------------------------------------------------------------------| | ||
| limit | specify the number of records to return. The default value is specified by the wallet. | | ||
| offset | specify which row to start retrieving from. The default value is 0. | | ||
|
||
```tsx | ||
getPublicKeys(params?: {limit?: number, offset?: number}): string[] | ||
``` | ||
|
||
### filterUnlockedCoins | ||
|
||
API accepts the `coinNames` array and returns unlocked ones. The `locked` logic is determined by the wallet and it means the dApp shoudn't use the coin in normal transactions. | ||
|
||
Normally, when the coin is used in some transaction and has not been confirmed, the coin is in `locked` state. | ||
|
||
```tsx | ||
filterUnlockedCoins(params: {coinNames: string[]}): string[] | ||
``` | ||
|
||
### getAssetCoins | ||
|
||
API returns the spendable coins for the selected assets. API will return all the available coins if the amount exceeds the spendable amount. | ||
|
||
When `type` is `did` or `nft`, the assetId can be null, which means no restrictions, and the API returns the corresponding type of coins. Also, when `type` is `cat`, `did` or `nft`, API will return an extra field called `lineageProof` to facilitate dApps to build `coinSpend` easily. In some cases, users may have many small amounts of coins. The wallet can allow users to choose whether to filter the coins. | ||
|
||
DApp can parse NFT and DID info from `puzzle` as needed. Please note that some results will skip or show up multiple times if the wallet is modified between the patinated calls. | ||
|
||
Parameters as described below. | ||
| Parameter | Description | | ||
|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| type | Asset type: `null`/cat/did/nft, `null` means `XCH` | | ||
| assetId | It depends on `type` and will be ignored when type is `null`. If type is `cat`, `assetId` means `tail program hash`. If `type` is `did`, `assetId` means `did id`. If `type` is `nft`, `assetId` means `nft id`. | | ||
| includedLocked | Whether to include `locked` coins. The default value is `false`. | | ||
| limit | specify the number of records to return. The default value is specified by the wallet. | | ||
| offset | specify which row to start retrieving from. The default value is 0. | | ||
|
||
```tsx | ||
interface getAssetCoinsParams { | ||
type: string|null; | ||
assetId: string|null; | ||
includedLocked?: boolean; | ||
offset?: number; | ||
limit?: number; | ||
} | ||
|
||
interface SpendableCoin { | ||
coin: Coin; | ||
coinName: string; | ||
puzzle: string; | ||
confirmedBlockIndex: number; | ||
locked: boolean; | ||
lineageProof?: { | ||
parentName?: string; | ||
innerPuzzleHash?: string; | ||
amount?: number; | ||
} | ||
} | ||
|
||
getAssetCoins(params: getAssetCoinsParams): SpendableCoin[] | ||
``` | ||
|
||
### getAssetBalance | ||
|
||
Returns the spendable balance of the wallet. It's convenient for the dApp to query the user's balance. Also, the dApp can sum the results by calling `getAssetCoins`. | ||
|
||
When `type` is `did` or `nft`, `assetId` can be `null`, which means no restriction. And API will return corresponding type. | ||
|
||
Parameters as described below. | ||
| Parameter | Description | | ||
|-----------|-----------------------------------------| | ||
| type | same as `type` in `selectAssetCoins` | | ||
| assetId | same as `assetId` in `selectAssetCoins` | | ||
|
||
```tsx | ||
interface AssetBalanceResp { | ||
confirmed: string; | ||
spendable: string; | ||
spendableCoinCount: number; | ||
} | ||
|
||
getAssetBalance(params: {type: string|null, assetId: string|null}): AssetBalanceResp | ||
``` | ||
|
||
|
||
### signCoinSpends | ||
|
||
This is a lower-level API that signs custom coin spends and returns the aggregated signature. The wallet should show as much information about the coinSpends to the user and ask the user for permissions. If the user rejects the request, API will throw an error. | ||
|
||
All the `coinSpend.coin` will be locked by the wallet. | ||
|
||
The API supports signing `synthetic public key` and `original public key`. The definition can be found in [the-chialisp](https://chialisp.com/docs/standard_transaction#the-chialisp). | ||
|
||
The API supports signing `AGG_SIG_ME` and `AGG_SIG_UNSAFE` conditions. The dApp can initiate fraudulent transactions by using `AGG_SIGN_UNSAFE` to do evil. The wallet need to request the user's double confirmation. | ||
|
||
The `partialSign` defaults to be false, and if `partialSign` is false, the wallet will sign all `AGG_SIG_ME` and `AGG_SIG_UNSAFE` conditions. Meanwhile, if there is a public key not owned by the wallet, `NoSecretKeyError` will be thrown. Otherwise, if the `partialSign` is true, the wallet will sign all the conditions that can be signed. | ||
|
||
For security reasons, the wallet should check whether the coin is valid or not. If the coin is not on the chain or not the children of the valid coin, it's invalid. The wallet should reject the sign request. | ||
|
||
Parameters as described below. | ||
| Parameter | Description | | ||
|-------------------------|--------------------------------------------------------------| | ||
| coinSpends | a list of `coinSpend` | | ||
| coinSpend.coin | the value is `Coin` | | ||
| coinSpend.puzzle_reveal | the puzzle of the Coin | | ||
| coinSpend.solution | the solution of puzzle | | ||
| partialSign | whether to support partialSign. The default value is `false` | | ||
|
||
```tsx | ||
signCoinSpends(params: {coinSpends: CoinSpend[], partialSign?: bool=false}): string | ||
``` | ||
|
||
### signMessage | ||
|
||
Sign the message encoded as a hex string using the private key associated with the public key. | ||
|
||
The internal implementation in the wallet is `bls_sign(private_key, sha256tree(cons("Chia Signed Message", message))`. The scheme used in `bls_sign` is Augmented Scheme. To prevent replay attacks, dApps should add the current `networkId` and `timestamp` into the message. If the dApp doesn't care which chain they're on, they can include the `timestamp` only. | ||
|
||
Parameters as described below. | ||
| Parameter | Description | | ||
|-----------|-----------------------------------| | ||
| message | the hex string needs to be signed | | ||
| publicKey | the public key managed by wallet | | ||
|
||
```tsx | ||
interface SignMessageParams { | ||
message: string; | ||
publicKey: string; | ||
} | ||
|
||
signMessage(params: SignMessageParams): string | ||
``` | ||
|
||
### sendTransaction | ||
|
||
Even if the wallet supports `sendTransaction`, we still highly recommend that the dApp uses its full node to broadcast transactions. | ||
|
||
```tsx | ||
interface SendTransactionParams { | ||
spendBundle: SpendBundle; | ||
} | ||
|
||
// stay the same as [transaction_ack](https://docs.chia.net/docs/10protocol/wallet_protocol/#transaction_ack) | ||
enum MempoolInclusionStatus { | ||
SUCCESS = 1 // Transaction added to mempool | ||
PENDING = 2 // Transaction not yet added to mempool | ||
FAILED = 3 // Transaction was invalid and dropped | ||
} | ||
|
||
interface TransactionResp { | ||
status: MempoolInclusionStatus; | ||
error?: string; | ||
} | ||
|
||
sendTransaction(params: SendTransactionParams): TransactionResp[] | ||
``` | ||
|
||
## Events | ||
|
||
### chainChanged | ||
|
||
The bridge emits `chainChanged` when connecting to a new chain. | ||
|
||
```tsx | ||
chia.on('chainChanged', listener: ({chainId: string}) => void) | ||
``` | ||
|
||
### accountChanged | ||
|
||
The bridge emits `accountChanged` when the user changes accounts, which means the user might change the `active key` and add a new `public key` or disable the `public key`. When the dApp receives this event, it should re-retrieve the wallet information. | ||
|
||
```tsx | ||
chia.on('accountChanged', listener: () => void) | ||
``` | ||
|
||
## Types | ||
|
||
```tsx | ||
interface Coin { | ||
parent_coin_info: string; | ||
puzzle_hash: string; | ||
amount: number; | ||
} | ||
|
||
interface CoinSpend { | ||
coin: Coin; | ||
puzzle_reveal: string; | ||
solution: string; | ||
} | ||
|
||
interface SpendBundle { | ||
coin_spends: CoinSpend[]; | ||
aggregated_signature: string; | ||
} | ||
``` | ||
|
||
## Errors | ||
|
||
```tsx | ||
interface Error { | ||
code: number; | ||
message: string; | ||
data?: any; | ||
} | ||
``` | ||
|
||
```tsx | ||
InvalidParamsError = { | ||
code: 4000, | ||
message: 'invalid params' | ||
} | ||
|
||
UnauthorizedError = { | ||
code: 4001, | ||
message: "unauthorized" | ||
} | ||
|
||
UserRejectedRequestError = { | ||
code: 4002, | ||
message: "user rejected request" | ||
} | ||
|
||
SpendableBalanceExceededError = { | ||
code: 4003, | ||
message: 'spendable balance exceeded' | ||
} | ||
|
||
MethodNotFoundError = { | ||
code: 4004, | ||
message: 'method not found' | ||
} | ||
|
||
NoSecretKeyError = { | ||
code: 4005, | ||
message: 'no secret key for public key' | ||
} | ||
|
||
LimitExceedError = { | ||
code: 4029, | ||
message: 'too many requests' | ||
} | ||
``` | ||
|
||
## Test Cases | ||
|
||
See [example.js](/notes/0002/example.js). | ||
|
||
## Security | ||
|
||
1. access control | ||
|
||
Except for `chainId` and `connect`, the dApp needs the read permission of the method before calling it. | ||
|
||
2. approval | ||
|
||
`signCoinSpends`, `signMessage`, `walletSwitchChain` methods need to be approved by the user before calling. | ||
|
||
## Additional Assets | ||
|
||
None. | ||
|
||
## Reference Implementation | ||
|
||
The [Goby](https://goby.app) team has implemented the methods described above. | ||
|
||
1. https://github.com/Chia-Network/chia-blockchain/blob/main/chia/rpc/wallet_rpc_api.py | ||
2. https://github.com/ChainSafe/web3.js | ||
3. https://github.com/cardano-foundation/CIPs/tree/master/CIP-0030 | ||
4. https://vacuumlabs.github.io/ledgerjs-cardano-shelley/5.0.0/index.html | ||
5. https://github.com/solana-labs/wallet-adapter | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). |
Oops, something went wrong.