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 |
This proposal describes a Web3 bridge between the browser wallets and dApps on the Chia Network.
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.
The parameters are currently passed via JSON
, which can be modified for subsequent expansion.
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.
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.
None.
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.
The apiVersion defined by this CHiP is 1.0.0
.
Return the current chainID.
CHAIN NAME | CHAINID |
---|---|
Chia Mainnet | mainnet |
Chia Testnet10 | testnet10 |
chainId(): string
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
The dApp requests to switch to another chain.
walletSwitchChain(params: {chainId: string}): void
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[]
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[]
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[]
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
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
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
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[]
The bridge emits chainChanged
when connecting to a new chain.
chia.on('chainChanged', listener: ({chainId: string}) => void)
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)
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;
}
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'
}
See example.js.
- access control
Except for chainId
and connect
, the dApp needs the read permission of the method before calling it.
- approval
signCoinSpends
, signMessage
, walletSwitchChain
methods need to be approved by the user before calling.
None.
The Goby team has implemented the methods described above.
- https://github.com/Chia-Network/chia-blockchain/blob/main/chia/rpc/wallet_rpc_api.py
- https://github.com/ChainSafe/web3.js
- https://github.com/cardano-foundation/CIPs/tree/master/CIP-0030
- https://vacuumlabs.github.io/ledgerjs-cardano-shelley/5.0.0/index.html
- https://github.com/solana-labs/wallet-adapter
Copyright and related rights waived via CC0.