Skip to content

Latest commit

 

History

History
395 lines (279 loc) · 15.1 KB

chip-0002.md

File metadata and controls

395 lines (279 loc) · 15.1 KB
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
Comments-URI #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

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
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.
connect(params: {eager?: boolean}): boolean

walletSwitchChain

The dApp requests to switch to another chain.

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

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

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

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.

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.

chia.on('accountChanged', listener: () => void)

Types

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

interface Error {
  code: number;
  message: string;
  data?: any;
}
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.

Security

  1. access control

Except for chainId and connect, the dApp needs the read permission of the method before calling it.

  1. approval

signCoinSpends, signMessage, walletSwitchChain methods need to be approved by the user before calling.

Additional Assets

None.

Reference Implementation

The Goby 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

Errata

Copyright

Copyright and related rights waived via CC0.