Skip to content

Commit e245ea4

Browse files
committed
ile/refactor
# Conflicts: # lerna.json # package.json # packages/background/package.json # packages/background/src/chains/service.ts # packages/common/package.json # packages/cosmos/package.json # packages/crypto/package.json # packages/ens/package.json # packages/extension/package.json # packages/extension/src/pages/main/token.tsx # packages/extension/src/pages/setting/token/add/index.tsx # packages/hooks/package.json # packages/hooks/src/tx/index.ts # packages/popup/package.json # packages/provider/package.json # packages/router/package.json # packages/stores/package.json # packages/stores/src/account/index.ts # packages/stores/src/query/queries.ts # packages/types/package.json # packages/unit/package.json # yarn.lock
2 parents 4ed9212 + c1df799 commit e245ea4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1474
-378
lines changed

.eslintignore

+4
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ Podfile.lock
1818
build.gradle
1919

2020
injected-provider.bundle.js
21+
22+
.gitsecret/*
23+
*.styl
24+
.github/*

.github/workflows/test.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ jobs:
1818
node-version: '12'
1919
- run: npm install --global yarn
2020
- run: sudo apt-get install libusb-1.0-0-dev
21-
- run: echo "deb https://dl.bintray.com/sobolevn/deb git-secret main" | sudo tee -a /etc/apt/sources.list
22-
- run: wget -qO - https://api.bintray.com/users/sobolevn/keys/gpg/public.key | sudo apt-key add -
23-
- run: sudo apt-get install apt-transport-https
21+
- run: sudo apt-get install apt-transport-https ca-certificates
22+
- run: sudo sh -c "echo 'deb https://gitsecret.jfrog.io/artifactory/git-secret-deb git-secret main' >> /etc/apt/sources.list"
23+
- run: wget -qO - 'https://gitsecret.jfrog.io/artifactory/api/gpg/key/public' | sudo apt-key add -
2424
- run: sudo apt-get update && sudo apt-get install git-secret
2525
- run: printf $GPG_KEY | base64 --decode > private.key
2626
env:

.gitsecret/keys/pubring.kbx

-691 Bytes
Binary file not shown.

.gitsecret/keys/pubring.kbx~

626 Bytes
Binary file not shown.

.gitsecret/paths/mapping.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
packages/extension/src/config.var.tsx:11885ce9605e19da65f1562b5b438d6646d301c83846acf9a10f2c7ce026de09
1+
packages/extension/src/config.var.tsx:8a8c6f61141b1afd821965d5701968e105d4a37d067049696d8ce55bd5799d1b
22
packages/extension/src/config.ui.var.tsx:8212e47fb357b8d61996b9f69fd7c865e5369de3e6264b1c81f3456a819c6578

.prettierignore

+4
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ Podfile.lock
1616
build.gradle
1717

1818
injected-provider.bundle.js
19+
20+
.gitsecret/*
21+
*.styl
22+
.github/*

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ This repository is still under development
99
This repository containts submodules that are not open sourced and are only available through the Chainapsis’ official Keplr Extension release. However, all primary features of the extension will work without the closed sourced submodules.
1010

1111
## Dev
12-
Keplr extension repo uses git-secret to encrypt the endpoints and the api keys. So, you can't build this without creating your own config file. You should create your own `config.var.ts` file inside the `src` folder. Refer to the `config.var.example.ts` sample file to create your own configuration.
12+
Keplr extension repo uses git-secret to encrypt the endpoints and the api keys. So, you can't build this without creating your own config file. You should create your own `config.var.ts`, `config.ui.var.ts` files inside the `packages/extension/src` folder. Refer to the `config.var.example.ts`, ``config.ui.var.example.ts`` sample files to create your own configuration.
1313
```sh
14-
npm run dev
14+
yarn dev
1515
```
16-
Extension's build output is placed in `/dist`, and you can check out [this page](https://developer.chrome.com/extensions/getstarted) for installing the developing extension.
16+
Extension's build output is placed in `packages/extension/dist`, and you can check out [this page](https://developer.chrome.com/extensions/getstarted) for installing the developing extension.
1717

1818
You can add your chain by adding the chain infomation into `chain-info.ts`.
1919

docs/.vuepress/config.js

+5
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,9 @@ module.exports = {
5555
},
5656
},
5757
plugins: [],
58+
markdown: {
59+
extendMarkdown: (md) => {
60+
md.use(require("markdown-it-container"), "suggest-chain-example-table");
61+
},
62+
},
5863
};

docs/.vuepress/styles/index.styl

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
11
:root
22
--color-primary #5064fb
3-
--color-link #5064fb
3+
--color-link #5064fb
4+
5+
.suggest-chain-example-table {
6+
table {
7+
thead {
8+
tr {
9+
th {
10+
min-width: 12rem;
11+
}
12+
}
13+
}
14+
}
15+
}

docs/api/README.md

+35-3
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,12 @@ Then, you can add the `@keplr-wallet/types` window to a global window object and
9797
### Enable Connection
9898

9999
```javascript
100-
enable(chainId: string): Promise<void>
100+
enable(chainIds: string | string[]): Promise<void>
101101
```
102102

103-
The `window.keplr.enable(chainId)` method requests the extension to be unlocked if it's currently locked. If the user hasn't given permission to the webpage, it will ask the user to give permission for the webpage to access Keplr.
103+
The `window.keplr.enable(chainIds)` method requests the extension to be unlocked if it's currently locked. If the user hasn't given permission to the webpage, it will ask the user to give permission for the webpage to access Keplr.
104+
105+
`enable` method can receive one or more chain-id as an array. When the array of chain-id is passed, you can request permissions for all chains that have not yet been authorized at once.
104106

105107
If the user cancels the unlock or rejects the permission, an error will be thrown.
106108

@@ -127,10 +129,12 @@ If the webpage has permission and Keplr is unlocked, this function will return t
127129
pubKey: Uint8Array;
128130
address: Uint8Array;
129131
bech32Address: string;
132+
isNanoLedger: boolean;
130133
}
131134
```
132135

133-
It also returns the nickname for the key store currently selected, which should allow the webpage to display the current key store selected to the user in a more convenient mane.
136+
It also returns the nickname for the key store currently selected, which should allow the webpage to display the current key store selected to the user in a more convenient mane.
137+
`isNanoLedger` field in the return type is used to indicate whether the selected account is from the Ledger Nano. Because current Cosmos app in the Ledger Nano doesn't support the direct (protobuf) format msgs, this field can be used to select the amino or direct signer. [Ref](./cosmjs.md#types-of-offline-signers)
134138

135139
### Sign Amino
136140

@@ -174,6 +178,34 @@ This function requests Keplr to delegates the broadcasting of the transaction to
174178
This method returns the transaction hash if it succeeds to broadcast, if else the method will throw an error.
175179
When Keplr broadcasts the transaction, Keplr will send the notification on the transaction's progress.
176180

181+
### Interaction Options
182+
183+
```javascript
184+
export interface KeplrIntereactionOptions {
185+
readonly sign?: KeplrSignOptions;
186+
}
187+
188+
export interface KeplrSignOptions {
189+
readonly preferNoSetFee?: boolean;
190+
readonly preferNoSetMemo?: boolean;
191+
}
192+
```
193+
Keplr v0.8.11+ offers additional options to customize interactions between the frontend website and Keplr extension.
194+
195+
If `preferNoSetFee` is set to true, Keplr will prioritize the frontend-suggested fee rather than overriding the tx fee setting of the signing page.
196+
197+
If `preferNoSetMemo` is set to true, Keplr will not override the memo and set fix memo as the front-end set memo.
198+
199+
You can set the values as follows:
200+
```javascript
201+
window.keplr.defaultOptions = {
202+
sign: {
203+
preferNoSetFee: true,
204+
preferNoSetMemo: true,
205+
}
206+
}
207+
```
208+
177209
## Custom event
178210

179211
### Change Key Store Event

docs/api/cosmjs.md

+15
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,27 @@ If the extension is already unlocked and the website has permission to connect,
4545

4646
`window.keplr.enable(chainId)` is not mandatory. Even if the method wasn't called, if an API that requests access to Keplr is called the flow above will automatically run. However, it is recommended that `window.keplr.enable(chainId)` is first run.
4747

48+
## Types of Offline Signers
49+
50+
In CosmJS, there are two types of Signers: OfflineSigner and OfflineDirectSigner. OfflineSigner is used to sign SignDoc serialized with Amino in Cosmos SDK Launchpad (Cosmos SDK v0.39.x or below). OfflineDirectSigner is used to sign Protobuf encoded SignDoc.
51+
52+
Keplr supports both types of Signers. Keplr’s `keplr.getOfflineSigner(chainId)` or `window.getOfflineSigner(chainId)` returns a Signer that satiesfies both the OfflineSigner and OfflineDirectSigner. Therefore, when using CosmJS with this Signer, Amino is used for Launchpad chains and Protobuf is used for Stargate chains.
53+
54+
However, if the msg to be sent is able to be serialized/deserialized using Amino codec you can use a signer for Amino. Also, as there are some limitations to protobuf type sign doc, there may be cases when Amino is necessary. For example, Protobuf formatted sign doc is currently not supprted by Ledger Nano’s Cosmos app. Also, because protobuf sign doc is binary formatted, msgs not natively supported by Keplr may not be human readable.
55+
56+
If you’d like to enforce the use of Amino, you can use the following APIs: `keplr.getOfflineSignerOnlyAmino(chainId)` or `window.getOfflineSignerOnlyAmino(chainId: string)`. Because this will always return an Amino compatible signer, any CosmJS requested msg that is Amino compatible will request a Amino SignDoc to Keplr.
57+
58+
Also, `window.getOfflineSignerAuto(chainId: string): Promise<OfflineSigner | OfflineDirectSigner>` or `window.getOfflineSignerAuto(chainId: string): Promise<OfflineSigner | OfflineDirectSigner>` API is supported. Please note that the value returned is async. This API automatically returns a signer that only supports Amino if the account is a Ledger-based account, and returns a signer that is compatible for both Amino and Protobuf if the account is a mnemonic/private key-based account. Because this API is affected by the type of the connected Keplr account, if [keplr_keystorechange](./README.md#change-key-store-event) event is used to detect account changes the signer must be changed using the API when this event has been triggered.
59+
4860
## Use with Stargate
4961

5062
Keplr's `OfflineSigner` implements the `OfflineDirectSigner` interface. Use `SigningStargateClient` with Keplr's `OfflineSigner`, and Keplr will sign the transaction in Proto sign doc format.
5163

5264
### Example
5365
Refer to the [keplr-example](https://github.com/chainapsis/keplr-example/blob/master/src/main.js) repository for example code on how to integrate Keplr with CosmJS.
5466

67+
### Interaction Options
68+
You can use Keplr native API’s to set interaction options even when using CosmJS. Please refer to [this section](./#interaction-options).
69+
5570
### Adding a custom blockchain to Keplr
5671
If Keplr doesn't natively support your blockchain within the extension, please refer to the [Suggest chain](./suggest-chain) section.

docs/api/secretjs.md

+3
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,6 @@ getSecret20ViewingKey(
5757
```
5858
Returns the viewing key of a SNIP-20 token registered in Keplr.
5959
If the SNIP-20 of the contract address doesn't exist, it will throw an error.
60+
61+
### Interaction Options
62+
You can use Keplr native API’s to set interaction options even when using SecretJS. Please refer to [this section](./#interaction-options).

docs/api/suggest-chain.md

+74-10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ order: 4
44
---
55

66
### Suggest Chain
7+
8+
*Warning: This is an experimental feature.*
9+
10+
Keplr's 'suggest chain' feature allows front-ends to request adding new Cosmos-SDK based blockchains that isn't natively integrated to Keplr extension.
11+
If the same chain is already added to Keplr, nothing will happen. If the user rejects the request, an error will be thrown.
12+
13+
This allows all Cosmos-SDK blockchains to have permissionless, instant wallet and transaction signing support for front-ends.
14+
15+
716
```javascript
817
interface ChainInfo {
918
readonly rpc: string;
@@ -50,16 +59,71 @@ interface ChainInfo {
5059
experimentalSuggestChain(chainInfo: SuggestingChainInfo): Promise<void>
5160
```
5261

53-
*Warning: This is an experimental feature.*
62+
#### Usage examples and recommendations
63+
64+
::: suggest-chain-example-table
65+
| Key | Example Value | Note |
66+
|-|-|-|
67+
| `rpc` | http://123.456.789.012:26657 | Address of RPC endpoint of the chain. Default port is 26657 |
68+
| `rest` | http://123.456.789.012:1317 | Address of REST/API endpoint of the chain. Default port is 1317. Must be enabled in `app.toml` |
69+
| `chainId` | mychain-1 | Keplr has a feature which automatically detects when the chain-id has changed, and automatically update to support new chain. However, it should be noted that this functionality will only work when the chain-id follows the {identifier}-{version}(ex.cosmoshub-4) format. Therefore, it is recommended that the chain follows the chain-id format. |
70+
| `stakeCurrency` | ```{ coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6, coinGeckoId: "cosmos", }``` | Information on the staking token of the chain |
71+
| `walletUrlForStaking` | https://wallet.keplr.app/#/cosmoshub/stake | The URL for the staking interface frontend for the chain. If you don't have a staking interface built, you can use [Lunie Light](https://github.com/luniehq/lunie-light) which supports Keplr. |
72+
| `bip44.coinType` | 118 | BIP44 coin type for address derivation. We recommend using `118`(Cosmos Hub) as this would provide good Ledger hardware wallet compatibility by utilizing the Cosmos Ledger app. |
73+
| `bech32Config` | ```{ bech32PrefixAccAddr: "cosmos", bech32PrefixAccPub: "cosmos" + "pub", bech32PrefixValAddr: "cosmos" + "valoper", bech32PrefixValPub: "cosmos" + "valoperpub", bech32PrefixConsAddr: "cosmos" + "valcons", bech32PrefixConsPub: "cosmos" + "valconspub"}``` | Bech32 config using the address prefix of the chain |
74+
| `currencies` | ```[ { coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6, coinGeckoId: "cosmos", }, ]``` | (TBD) |
75+
| `feeCurrencies` | ```[ { coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6, coinGeckoId: "cosmos", }, ]``` | List of fee tokens accepted by the chain's validator. |
76+
| `gasPriceStep` | ```{ low: 0.01, average: 0.025, high: 0.03, }``` | Three `gasPrice` values (low, average, high) to estimate transaction fee. |
77+
| `features` | [stargate] | `secretwasm` - Secret Network WASM smart contract transaction support `stargate` - For Cosmos SDK blockchains using cosmos-sdk v0.4+. (However, even if the `stargate` isn't set, Keplr will query "/cosmos/base/tendermint/v1beta1/node_info" to check if it succeeds. If successful, Keplr will assume that a gRPC HTTP gateway available and automatically set it as Stargate) `ibc-transfer` - For IBC transfers (ICS 20) enabled chains. For Stargate (cosmos-sdk v0.40+) chains, Keplr will check the on-chain params and automatically enable IBC transfers if it’s available) `cosmwasm` - For CosmWasm smart contract support (currently broken, in the process of being fixed) |
78+
:::
5479

55-
It allows a blockchain that isn't natively integrated by Keplr to be added to Keplr extension.
56-
If the same chain is already added to Keplr, nothing will happen. If the user rejects the request, an error will be thrown.
57-
58-
For Stargate Cosmos-SDK chains, it is recommended to put the `features` value as `stargate`.
59-
(However, even if the `stargate` isn't set, Keplr will query "/cosmos/base/tendermint/v1beta1/node_info" to check if it succeeds. If successful, Keplr will assume that a gRPC HTTP gateway available and automatically set it as Stargate).
80+
Copy and paste example:
81+
```javascript
82+
await window.keplr.experimentalSuggestChain({
83+
chainId: "mychain-1",
84+
chainName: "my new chain",
85+
rpc: "http://123.456.789.012:26657",
86+
rest: "http://123.456.789.012:1317",
87+
bip44: {
88+
coinType: 118,
89+
},
90+
bech32Config: {
91+
bech32PrefixAccAddr: "cosmos",
92+
bech32PrefixAccPub: "cosmos" + "pub",
93+
bech32PrefixValAddr: "cosmos" + "valoper",
94+
bech32PrefixValPub: "cosmos" + "valoperpub",
95+
bech32PrefixConsAddr: "cosmos" + "valcons",
96+
bech32PrefixConsPub: "cosmos" + "valconspub",
97+
},
98+
currencies: [
99+
{
100+
coinDenom: "ATOM",
101+
coinMinimalDenom: "uatom",
102+
coinDecimals: 6,
103+
coinGeckoId: "cosmos",
104+
},
105+
],
106+
feeCurrencies: [
107+
{
108+
coinDenom: "ATOM",
109+
coinMinimalDenom: "uatom",
110+
coinDecimals: 6,
111+
coinGeckoId: "cosmos",
112+
},
113+
],
114+
stakeCurrency: {
115+
coinDenom: "ATOM",
116+
coinMinimalDenom: "uatom",
117+
coinDecimals: 6,
118+
coinGeckoId: "cosmos",
119+
},
120+
coinType: 118,
121+
gasPriceStep: {
122+
low: 0.01,
123+
average: 0.025,
124+
high: 0.03,
125+
},
126+
});
127+
```
60128

61-
Keplr has a feature which automatically detects when the chain-id has changed, and automatically update to support new chain. However, it should be noted that this functionality will only work when the chain-id follows the `{identifier}-{version}` (ex. `cosmoshub-4`) format. Therefore, it is recommended that the chain follows the chain-id format.
62-
63-
If the chain supports Secret WASM contracts, you may input `secretwasm` as a value for `features` to enable Secret WASM support.
64-
65129
Keplr supports the basic the `x/bank` module's send feature and balance query. Also, it is able to show the staking reward percentage from the `supply` and `mint` module. (For Stargate chains, Keplr will find the supply through the `bank` module).

docs/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
"author": "chainapsis",
1010
"license": "Apache-2.0",
1111
"dependencies": {
12-
"vuepress-theme-cosmos": "1.0.179",
12+
"markdown-it": "12.0.4",
13+
"markdown-it-container": "^3.0.0",
1314
"vuepress": "1.8.2",
14-
"markdown-it": "12.0.4"
15+
"vuepress-theme-cosmos": "1.0.179"
1516
}
1617
}

docs/yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -5326,6 +5326,11 @@ markdown-it-container@^2.0.0:
53265326
resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-2.0.0.tgz#0019b43fd02eefece2f1960a2895fba81a404695"
53275327
integrity sha1-ABm0P9Au7+zi8ZYKKJX7qBpARpU=
53285328

5329+
markdown-it-container@^3.0.0:
5330+
version "3.0.0"
5331+
resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-3.0.0.tgz#1d19b06040a020f9a827577bb7dbf67aa5de9a5b"
5332+
integrity sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==
5333+
53295334
markdown-it-emoji@^1.4.0:
53305335
version "1.4.0"
53315336
resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz#9bee0e9a990a963ba96df6980c4fddb05dfb4dcc"

etc/noop/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This directory isn’t actually used, and would be preferred to not be added. But it is needed to explicitly ignore specific libraries used by the dependency. Specifically, libsodium isn’t actually used and WebAssembly can’t be used in the extension’s sandbox environment but creates various errors so the directory exists to ignore libsodium.

etc/noop/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// eslint-disable-next-line import/no-default-export
2+
export default {};

etc/noop/package.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "noop",
3+
"version": "0.0.1",
4+
"main": "index.js",
5+
"private": true,
6+
"scripts": {},
7+
"dependencies": {}
8+
}

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"build": "lerna run build",
3030
"build:libs": "lerna run build --ignore @keplr-wallet/extension",
3131
"test": "lerna run test --parallel",
32+
"build:clean": "yarn clean && yarn install --frozen-lockfile && lerna run build",
3233
"lint-test": "lerna run lint-test",
3334
"lint-fix": "lerna run lint-fix",
3435
"ci": "yarn install --frozen-lockfile && yarn build && yarn test",
@@ -75,6 +76,8 @@
7576
"resolutions": {
7677
"@types/react": "^16.14.4",
7778
"@cosmjs/crypto": "^0.24.0-alpha.25",
78-
"@iov/crypto": "2.1.0"
79+
"@iov/crypto": "2.1.0",
80+
"libsodium": "file:./etc/noop",
81+
"libsodium-wrappers": "file:./etc/noop"
7982
}
8083
}

packages/background/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"ledger-cosmos-js": "^2.1.8",
4646
"reflect-metadata": "^0.1.13",
4747
"secp256k1": "^4.0.2",
48-
"secretjs": "^0.10.2",
48+
"secretjs": "^0.16.0",
4949
"tsyringe": "^4.4.0"
5050
}
5151
}

packages/background/src/keyring/handler.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ const handleGetKeyMsg: (
247247
(await service.chainsService.getChainInfo(msg.chainId)).bech32Config
248248
.bech32PrefixAccAddr
249249
),
250+
isNanoLedger: key.isNanoLedger,
250251
};
251252
};
252253
};
@@ -265,7 +266,8 @@ const handleRequestSignAminoMsg: (
265266
env,
266267
msg.chainId,
267268
msg.signer,
268-
msg.signDoc
269+
msg.signDoc,
270+
msg.signOptions
269271
);
270272
};
271273
};
@@ -286,7 +288,8 @@ const handleRequestSignDirectMsg: (
286288
env,
287289
msg.chainId,
288290
msg.signer,
289-
signDoc
291+
signDoc,
292+
msg.signOptions
290293
);
291294

292295
return {

packages/background/src/keyring/keyring.ts

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface Key {
2525
algo: string;
2626
pubKey: Uint8Array;
2727
address: Uint8Array;
28+
isNanoLedger: boolean;
2829
}
2930

3031
export type MultiKeyStoreInfoElem = Pick<
@@ -576,6 +577,7 @@ export class KeyRing {
576577
algo: "secp256k1",
577578
pubKey: pubKey.toBytes(),
578579
address: pubKey.getAddress(),
580+
isNanoLedger: true,
579581
};
580582
} else {
581583
const privKey = this.loadPrivKey(coinType);
@@ -585,6 +587,7 @@ export class KeyRing {
585587
algo: "secp256k1",
586588
pubKey: pubKey.toBytes(),
587589
address: pubKey.getAddress(),
590+
isNanoLedger: false,
588591
};
589592
}
590593
}

0 commit comments

Comments
 (0)