Skip to content
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

CHIP-0002: Add dApp protocol #9

Merged
merged 33 commits into from
Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
55a14ce
Add dApp protocol proposal
Apr 20, 2022
21db5ff
update some methods, add rationale & reference implementation setctions
Apr 25, 2022
01b4ffc
update methods and remove createAssetTx
Apr 27, 2022
d0ea2b9
add getPublicKeyByAddress method & additional description
Apr 28, 2022
b4dad7c
change to CHIP0002 & Draft status
Apr 28, 2022
c8b9e60
remove IETF RFC Spec & update Comments-URI
Apr 29, 2022
8543505
Merge branch 'Chia-Network:main' into chip0002
Apr 29, 2022
d3026ea
update methods
Apr 29, 2022
fc9140d
format & add types
May 10, 2022
2868c7d
change to browser.tabs.sendMessage & fix typo
May 12, 2022
53f5763
format
May 13, 2022
5f7a07d
remove Transfer, CreateOffer & TakeOffer
May 15, 2022
ea6d43f
add a `puzzle` return parameter & remove duplicated CoinSpend
May 25, 2022
36b488f
format
May 25, 2022
39fec69
unify function naming
Jun 5, 2022
c18a8dd
update rationale
Jun 5, 2022
67731b5
add more desc & update security section
Jun 7, 2022
47b542b
add example.js
Jun 8, 2022
aaa1c61
support NFT & DID
Jun 16, 2022
7d8ff8d
format
Jun 16, 2022
20c9980
simplify the APIs & add pagination
Jun 25, 2022
1953e38
update `getPublicKeys` desc
Jun 28, 2022
71820aa
fix typos
Jul 10, 2022
fc8be6a
detect wallet name
Jul 12, 2022
eef816d
add apiVersion & wallet version
Jul 17, 2022
c9f162f
add newly-defined functions in example.js
Aug 10, 2022
c75136c
change to Review status
Aug 17, 2022
1a67cbf
change pagination of getAssetCoins
Aug 26, 2022
061de4c
suppor partialSign
Sep 5, 2022
5fae81d
update to Last Call
Sep 13, 2022
73061c3
change signMessage method & revert to Review status
Oct 1, 2022
35e9f82
update to Last Call
Oct 11, 2022
7434568
Change status of 0002
Oct 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>;
mariano54 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make it clear that any request might take a while (Several minutes) because the user has to approve manually. Is this true?

```

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`.

mariano54 marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a convenience method, because we can call selectAssetCoins and add up the amount right?


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;
}
```

mariano54 marked this conversation as resolved.
Show resolved Hide resolved
## 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