Skip to content

Commit

Permalink
CHIP-0002: Add dApp protocol (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
dimitry committed Oct 25, 2022
1 parent e327a66 commit b45154f
Show file tree
Hide file tree
Showing 2 changed files with 569 additions and 0 deletions.
393 changes: 393 additions & 0 deletions CHIPs/chip-0002.md
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/).
Loading

0 comments on commit b45154f

Please sign in to comment.