remove docs folder from repo (moved to https://hackmd.io/@status-desktop/B1eOaf-nd)
This commit is contained in:
parent
1d3e5230b2
commit
8cce490407
|
@ -0,0 +1,8 @@
|
|||
# Status-desktop
|
||||
|
||||
Desktop client for the [Status Network](https://statusnetwork.com/) built with [Nim](https://nim-lang.org/) and [Qt](https://www.qt.io/)
|
||||
|
||||
![https://github.com/status-im/nim-status-client/blob/master/screenshot.png](https://github.com/status-im/nim-status-client/blob/master/screenshot.png)
|
||||
|
||||
Dev Docs: [https://hackmd.io/@status-desktop/B1eOaf-nd](https://hackmd.io/@status-desktop/B1eOaf-nd)
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
# Status-desktop
|
||||
|
||||
Desktop client for the [Status Network](https://statusnetwork.com/) built with [Nim](https://nim-lang.org/) and [Qt](https://www.qt.io/)
|
||||
|
||||
![https://github.com/status-im/nim-status-client/blob/master/docs/screenshot.png](https://github.com/status-im/nim-status-client/blob/master/docs/screenshot.png)
|
||||
|
||||
*note: this documentation is wip*
|
||||
|
||||
### Getting started
|
||||
* [Building Nim-Status-Client](https://github.com/status-im/nim-status-client/blob/master/docs/building.md)
|
||||
|
||||
### Overview
|
||||
* [QML Crash Course](https://github.com/status-im/nim-status-client/blob/master/docs/qml_crash_course.md)
|
||||
* [NimQml](https://github.com/status-im/nim-status-client/blob/master/docs/qml_nim_communication.md)
|
||||
|
||||
### Architecture & Development
|
||||
* [Project Architecture & Structure](https://github.com/status-im/nim-status-client/blob/master/docs/structure.md)
|
||||
* [Common development errors aand how to solve them](https://github.com/status-im/nim-status-client/blob/master/docs/common_errors.md)
|
||||
* [tips & tricks](https://github.com/status-im/nim-status-client/blob/master/docs/tips.md)
|
||||
|
||||
### Guides & Tutorials
|
||||
* [Tutorial - how to add a new section](https://github.com/status-im/nim-status-client/blob/master/docs/tutorial_adding_section.md)
|
||||
* [Tutorial - how to create a custom QML component](https://github.com/status-im/nim-status-client/blob/master/docs/tutorial_custom_component.md)
|
||||
|
||||
### Continuous Integration
|
||||
* [CI Readme](./ci/README.md)
|
||||
* [Jenkins Jobs](https://ci.status.im/job/nim-status-client/)
|
||||
|
||||
### API
|
||||
* [QML Nim-Status-Client API reference](https://github.com/status-im/nim-status-client/blob/master/docs/qml_api.md)
|
||||
|
||||
### Translations
|
||||
* [How to translate and use automatic scripts](https://github.com/status-im/nim-status-client/blob/master/scripts/translationScripts/README.md)
|
||||
|
||||
### License
|
||||
|
||||
Licensed under the [Mozilla Public License Version 2.0](https://github.com/status-im/nim-status-client/blob/master/LICENSE.md)
|
||||
|
160
docs/building.md
160
docs/building.md
|
@ -1,160 +0,0 @@
|
|||
## Building
|
||||
|
||||
### 0. Prerequesites
|
||||
|
||||
On windows you can simply run the [`scripts/windows_build_setup.ps1`](../scripts/windows_build_setup.ps1) script in a PowerShell with Administrator privileges.
|
||||
|
||||
* QT
|
||||
|
||||
**IMPORTANT:** Due to [a bug](https://github.com/status-im/status-desktop/commit/7b07a31fa6d06c730cf563475d319f0217a211ca) in version `5.15.0`, this project is locked to version `5.14.2`. Make sure to select version `5.14.2` when installing Qt via the installer.
|
||||
|
||||
Linux users should install Qt through the system's package manager:
|
||||
|
||||
```
|
||||
# Debian/Ubuntu:
|
||||
sudo apt install qtbase5-dev qtdeclarative5-dev qml-module-qt-labs-platform qtquickcontrols2-5-dev
|
||||
|
||||
# Fedora
|
||||
sudo dnf install qt-devel qt5-devel
|
||||
|
||||
```
|
||||
|
||||
If that's not possible, manually install QT from https://www.qt.io/download-qt-installer
|
||||
and add it to the PATH
|
||||
|
||||
```
|
||||
# Linux
|
||||
export PATH=$PATH:/path/to/Qt/5.14.2/gcc_64/bin
|
||||
|
||||
# macos
|
||||
export PATH=$PATH:/path/to/Qt/5.14.2/clang_64/bin
|
||||
```
|
||||
|
||||
* Go - (used to build status-go)
|
||||
|
||||
```
|
||||
# Linux
|
||||
<TODO>
|
||||
|
||||
# macOS
|
||||
brew install go
|
||||
```
|
||||
|
||||
### 1. Install QT, and add it to the PATH
|
||||
|
||||
```
|
||||
# Linux users should use their distro's package manager, but in case they do a manual install:
|
||||
export QTDIR="/path/to/Qt/5.14.2/gcc_64"
|
||||
export PATH="${QTDIR}/bin:${PATH}"
|
||||
|
||||
# macOS:
|
||||
export QTDIR="/path/to/Qt/5.14.2/clang_64"
|
||||
export PATH="${QTDIR}/bin:${PATH}"
|
||||
```
|
||||
|
||||
### 2. Clone the repo and build `nim-status-client`
|
||||
```
|
||||
git clone https://github.com/status-im/nim-status-client
|
||||
cd nim-status-client
|
||||
make update
|
||||
make
|
||||
```
|
||||
|
||||
For more output use `make V=1 ...`.
|
||||
|
||||
Use 4 CPU cores with `make -j4 ...`.
|
||||
|
||||
Users with manually installed Qt5 packages need to run `make QTDIR="/path/to/Qt" ...`
|
||||
|
||||
**Troubleshooting**:
|
||||
|
||||
If the `make` command fails due to already installed Homebrew packages, such as:
|
||||
|
||||
```
|
||||
Error: protobuf 3.11.4 is already installed
|
||||
To upgrade to 3.11.4_1, run `brew upgrade protobuf`.
|
||||
make[1]: *** [install-os-dependencies] Error 1
|
||||
make: *** [vendor/status-go/build/bin/libstatus.a] Error 2
|
||||
```
|
||||
|
||||
This can be fixed by uninstalling the package e.g. `brew uninstall protobuf` followed by rerunning `make`.
|
||||
|
||||
|
||||
### 3. Run the app
|
||||
|
||||
```
|
||||
make run
|
||||
# or
|
||||
LD_LIBRARY_PATH=vendor/DOtherSide/lib ./bin/nim_status_client
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
If only making changes in QML `ui/` re-rerunning the app is enough
|
||||
If making changes in the nim code `src/` then doing `make` again is needed (it's very fast after the first run)
|
||||
|
||||
## Cold Reload
|
||||
|
||||
### "Cold" reload using VSCode
|
||||
|
||||
We can setup a "cold" reload, whereby the app will be rebuilt and restarted when changes in the source are saved. This will not save state, as the app will be restarted, but it will save us some time from manually restarting the app. We can handily force an app rebuild/relaunch with the shortcut `Cmd+Shift+b` (execute the default build task, which we'll setup below).
|
||||
|
||||
To enable a meagre app reload during development, first creates a task in `.vscode/tasks.json`. This task sets up the default build task for the workspace, and depends on the task that compiles our nim:
|
||||
|
||||
```json
|
||||
({
|
||||
"label": "Build Nim Status Client",
|
||||
"type": "shell",
|
||||
"command": "nim",
|
||||
"args": [
|
||||
"c",
|
||||
"-L:lib/libstatus.dylib",
|
||||
"-L:-lm",
|
||||
"-L:\"-framework Foundation -framework Security -framework IOKit -framework CoreServices\"",
|
||||
"--outdir:./bin",
|
||||
"src/nim_status_client.nim"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceRoot}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run nim_status_client",
|
||||
"type": "shell",
|
||||
"command": "bash",
|
||||
"args": ["./run.sh"],
|
||||
"options": {
|
||||
"cwd": "${workspaceRoot}/.vscode"
|
||||
},
|
||||
"dependsOn": ["Build Nim Status Client"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Next, add a `.vscode/run.sh` file, changing the `DOtherSide` lib path to be specific to your environment:
|
||||
|
||||
```bash
|
||||
export LD_LIBRARY_PATH="/Users/emizzle/repos/github.com/filcuc/DOtherSide/build/lib"
|
||||
../bin/nim_status_client
|
||||
```
|
||||
|
||||
# Auto build on save (for the "cold" reload effect)
|
||||
|
||||
Finally, to get trigger this default build task when our files our saved, we need to enable a task to be run while `.nim` files are saved, and when `.qml` files are saved.
|
||||
|
||||
### Build on save
|
||||
|
||||
To build on save of our source files, first install the "Trigger Task on Save" VS Code extension to detect changes to our changable files, which will trigger a build/run. Once installed, update `settings.json` like so:
|
||||
|
||||
```json
|
||||
"files.autoSave": "afterDelay",
|
||||
"triggerTaskOnSave.tasks": {
|
||||
"Run nim_status_client": ["ui/**/*", "src/*.nim"]
|
||||
},
|
||||
"triggerTaskOnSave.restart": true,
|
||||
"triggerTaskOnSave.showStatusBarToggle": true
|
||||
|
||||
```
|
|
@ -1,91 +0,0 @@
|
|||
## Common errors and how to solve them
|
||||
|
||||
### SIGSEGV: Illegal storage access. (Attempt to read from nil?)
|
||||
|
||||
This happens due to using a null pointer, it can be caused by several situations:
|
||||
|
||||
**calling status-go with invalid parameters**
|
||||
|
||||
Calling status-go with a json that is missing a field somewhere can cause status-go to crash somewhere or throw an exception that is not being caught
|
||||
|
||||
**listening for non existing events**
|
||||
|
||||
If an event in which a corresponding `emit` does not exist, it can cause this error
|
||||
|
||||
```nimrod=
|
||||
events.on("event-does-not-exist") do(a: Args):
|
||||
appState.addChannel("test")
|
||||
appState.addChannel("test2")
|
||||
```
|
||||
|
||||
**parsing json**
|
||||
|
||||
when working with json, this error could be triggered for several reasons
|
||||
* accessing a property that doesn't exist
|
||||
* get the value type that doesn't match the value in the json
|
||||
* `if payload["contentType"].str == 2:` will crash because the value of contentType is `2` not `"2"`
|
||||
* something extracting a string with `value.str` instead of `$value` (sometimes `.getStr`)
|
||||
|
||||
### Error: attempting to call undeclared routine
|
||||
|
||||
this happens due something missing in the QTObject, it's caused for when a proc is not marked as a slot, not being public, not part of a variant, missing the self attribute or not mapped in a qproperty if it is an accesor
|
||||
|
||||
*TODO: add practical examples*
|
||||
|
||||
### Unsupported conversion of X to metatype
|
||||
|
||||
this can happen due to a method being exposed to QT as a slot but using an object (like a model X) that is not a QtObject.
|
||||
possible solutions:
|
||||
- make the object a QtObject (usually only recommended if it's in the view only)
|
||||
- remove the {.slot.} pragma if it's not being called from QML anyway
|
||||
- change the method to receive params individually and then build the model inside the method
|
||||
|
||||
### typeless parameters are obsolete
|
||||
|
||||
typically means types are missing for a method parameters
|
||||
|
||||
### attempting to call undeclared routine
|
||||
|
||||
routine is likely not public or is being declared after the method calling it
|
||||
|
||||
### QML Invalid component body specification
|
||||
|
||||
This error happens when a `Component` has multiple children, it must only contain one child, to fix it, put the component children inside a `Item {}`
|
||||
|
||||
### QML TypeError: Property 'setFunctionName' of object SomeView(0x7fa4bf55b240) is not a function
|
||||
|
||||
Likely the function is missing a `{.slot.}` pragma
|
||||
|
||||
### QML input text value not being updated
|
||||
|
||||
If you are using an `Input` QML prop, to get the current value use `idName.TextField.text` instead of `idName.text`
|
||||
|
||||
### QMutex: destroying locked mutex
|
||||
|
||||
a common scenario this error can happen is when trying to immediatly access something in status-go when the app starts before the node is ready. it can also happen due to 2 threads attempting to call & change something from status-go at the same time
|
||||
|
||||
## Warnings
|
||||
|
||||
### QML anchor warnings
|
||||
|
||||
Those look like
|
||||
```
|
||||
Cannot specify top, bottom, verticalCenter, fill or centerIn anchors for items inside Column. Column will not function.
|
||||
```
|
||||
or
|
||||
```
|
||||
Detected anchors on an item that is managed by a layout. This is undefined behavior; use Layout.alignment instead.
|
||||
```
|
||||
|
||||
Those mean that you used anchors on an element that is manged by a Layout. Those are ColumnLayouts, StackLayouts, etc.
|
||||
|
||||
The first child of anything in a "Something"Layout will not have access to anchors (they will throw warnings).
|
||||
|
||||
First thing to ask yourself, do you really need a Layout? That's the easiest way to fix it. Unless you really need your block to be a row or a column that needs to go next/under another, use an Item or similar. Usually, you can still do the same effect anyway with anchors on the siblings
|
||||
|
||||
If you really need the Layout, then one way to fix is to set the first child of the Layout an `Item` and then every other child inside the `Item`. That way, all the children can use anchors. You can set
|
||||
```
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
```
|
||||
on the `Item` to make it fill the whole parent so that nothing else needs to be changed.
|
Binary file not shown.
Before Width: | Height: | Size: 68 KiB |
|
@ -1,75 +0,0 @@
|
|||
# Description
|
||||
|
||||
This document describes the process of notarizing a MacOS application.
|
||||
|
||||
# Notarization
|
||||
|
||||
The process [Software Notarization](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution) is necessary to avoid [Gatekeeper](https://en.wikipedia.org/wiki/Gatekeeper_(macOS)) warnings which look like this:
|
||||
|
||||
![Gatekeeper Warning](./images/gatekeeper_warning.png)
|
||||
|
||||
According to Apple the Notarization process will:
|
||||
|
||||
>Give users even more confidence in your software by submitting it to Apple to be notarized. The service automatically scans your Developer ID-signed software and performs security checks. When it's ready to export for distribution, a ticket is attached to your software to let Gatekeeper know it's been notarized.
|
||||
|
||||
https://developer.apple.com/developer-id/
|
||||
|
||||
The process involves the following steps:
|
||||
|
||||
>When you click Next, Xcode uploads your archive to the notary service. When the upload is complete, the notary service begins the scanning process, which usually takes less than an hour. (...) When the notarization process finishes, Xcode downloads the ticket and staples it to your archive. At that point, export your archive again to receive a distributable version of your software that includes the notary ticket.
|
||||
|
||||
https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution
|
||||
|
||||
# Tickets
|
||||
|
||||
>Notarization produces a ticket that tells Gatekeeper that your app is notarized. After notarization completes successfully, the next time any user attempts to run your app on macOS 10.14 or later, Gatekeeper finds the ticket online. This includes users who downloaded your app before notarization.
|
||||
|
||||
https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow
|
||||
|
||||
# Authentication
|
||||
|
||||
The notarization process requires authentication.
|
||||
|
||||
>Add the `username` and `password` options to supply your App Store Connect credentials. Because App Store Connect now requires two-factor authentication (2FA) on all accounts, you must create an app-specific password for `altool`, as described in Using app-specific passwords.
|
||||
|
||||
https://support.apple.com/en-us/HT204397
|
||||
|
||||
# Tools
|
||||
|
||||
Notarization can be performed by Xcode, or using the command line `xcrun altool` utility:
|
||||
```sh
|
||||
% xcrun altool --notarize-app
|
||||
--primary-bundle-id "com.example.ote.zip"
|
||||
--username "AC_USERNAME"
|
||||
--password "@keychain:AC_PASSWORD"
|
||||
--asc-provider <ProviderShortname>
|
||||
--file OvernightTextEditor_11.6.8.zip
|
||||
```
|
||||
The request is created which has a UUID assigned to it which can be used to check progress:
|
||||
```sh
|
||||
% xcrun altool --notarization-info 2EFE2717-52EF-43A5-96DC-0797E4CA1041 -u "AC_USERNAME"
|
||||
```
|
||||
And once completed the ticket can be "stapled" to the bundle:
|
||||
```
|
||||
% xcrun stapler staple "Overnight TextEditor.app"
|
||||
```
|
||||
https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow
|
||||
|
||||
# Script
|
||||
|
||||
Our process is automated using the [`scripts/notarize-macos-pkg.sh`](../scripts/notarize-macos-pkg.sh) script, which performs all the necessary steps in CI:
|
||||
```
|
||||
../scripts/notarize-macos-pkg.sh pkg/StatusIm-Desktop-v0.1.0-beta.9-39582e.dmg
|
||||
```
|
||||
But it requires certain credentials to be provided:
|
||||
|
||||
* `MACOS_NOTARIZE_TEAM_ID` - Apple Team ID
|
||||
* `MACOS_NOTARIZE_USERNAME` - Apple Dev Portal Username
|
||||
* `MACOS_NOTARIZE_PASSWORD` - Apple Dev Portal Password or Keystore with password
|
||||
|
||||
# Links
|
||||
|
||||
* https://scriptingosx.com/2019/09/notarize-a-command-line-tool/
|
||||
* https://stackoverflow.com/questions/56890749/macos-notarize-in-script
|
||||
* https://github.com/rednoah/notarize-app
|
||||
* https://support.apple.com/en-us/HT204397
|
296
docs/qml_api.md
296
docs/qml_api.md
|
@ -1,296 +0,0 @@
|
|||
## API available to QML
|
||||
|
||||
### Wallet Model
|
||||
The wallet model (exposed as `walletModel`) is used for functions pertaining to the wallet and accounts on the node.
|
||||
|
||||
#### Signals
|
||||
The `walletModel` exposes the following signals, which can be consumed in QML using the `Connections` component (with a target of `walletModel` and prefixed with `on`).
|
||||
| Name | Parameters | Description |
|
||||
|---------------|----------|--------------|
|
||||
| `etherscanLinkChanged` | none | fired when the etherscan link has changed |
|
||||
| `signingPhraseChanged` | none | fired when the signing phrase has changed |
|
||||
| `currentCollectiblesListsChanged` | none | fired when the list of collectibles for the currently selected account has changed |
|
||||
| `currentTransactionsChanged` | none | fired when the transactions for the currently selected account have changed |
|
||||
| `currentAccountChanged` | none | fired when the currently selected account in the wallet has changed |
|
||||
| `focusedAccountChanged` | none | fired when the currently selected account in the chat transaction model has changed |
|
||||
| `currentAssetListChanged` | none | fired when the token assets for the currently selected account have changed |
|
||||
| `totalFiatBalanceChanged` | none | fired when the total equivalent fiat balance of all accounts has changed |
|
||||
| `accountListChanged` | none | fired when accounts on the node have chagned |
|
||||
| `transactionWasSent` | `txResult` (`string`): JSON stringified result of sending a transaction | fired when accounts on the node have chagned |
|
||||
| `defaultCurrencyChanged` | none | fired when the user's default currency has chagned |
|
||||
| `gasPricePredictionsChanged` | none | fired when the gas price predictions have changed, typically after getting a gas price prediction response |
|
||||
| `loadingTrxHistoryChanged` | `isLoading` (`bool`): `true` if the transaction history is loading | fired when the loading of transfer history starts and completes |
|
||||
| `ensWasResolved` | `resolvedAddress` (`string`): address resolved from the ENS name<br>`uuid` (`string`): unique identifier that was used to identify the request in QML so that only specific components can respond when needed | fired when an ENS name was resolved |
|
||||
| `transactionCompleted` | `success` (`bool`): `true` if the transaction was successful<br>`txHash` (`string`): has of the transaction<br>`revertReason` (`string`): reason transaction was reverted (if provided and if the transaction was reverted) | fired when a tracked transction (from the wallet or ENS) was completed |
|
||||
| `dappBrowserAccountChanged` | none | fired when the select dapp browser wallet account has changed |
|
||||
|
||||
### QtProperties
|
||||
The following properties can be accessed directly on the `walletModel`, ie `walletModel.etherscanLink`
|
||||
| Name | Type | Accessibility | Signal | Description |
|
||||
|---------------|------|---------------|--------|--------------|
|
||||
| `etherscanLink` | `QVariant<string>` | `read` | `etherscanLinkChanged` | link to Etherscan from the current network settings |
|
||||
| `signingPhrase` | `QVariant<string>` | `read` | `signingPhraseChanged` | gets the signing phrase |
|
||||
| `collectiblesLists` | `QVariant<CollectiblesList>` | `read`/`write` | `currentCollectiblesListsChanged` | gets or sets the list of collectibles for the currently selected wallet |
|
||||
| `transactions` | `QVariant<TransactionList>` | `read`/`write` | `currentTransactionsChanged` | gets or sets the list of transactions for the currently selected wallet |
|
||||
| `currentAccount` | `QVariant<AccountItemView>` | `read`/`write` | `currentAccountChanged` | gets or sets the currently selected account |
|
||||
| `focusedAccount` | `QVariant<AccountItemView>` | `read`/`write` | `focusedAccountChanged` | gets or sets the currently focused account in the chat transaction modal |
|
||||
| `assets` | `QVariant<AssetList>` | `read`/`write` | `currentAssetListChanged` | gets or sets list of token assets for the currently selected wallet account |
|
||||
| `totalFiatBalance` | `QVariant<string>` | `read`/`write` | `totalFiatBalanceChanged` | gets or sets the total equivalent fiat balance of all wallets in the format `#.##` |
|
||||
| `accounts` | `QVariant<AccountList>` | `read` | `accountListChanged` | returns list of accounts on the node |
|
||||
| `defaultCurrency` | `QVariant<string>` | `read`/`write` | `defaultCurrencyChanged` | gets or sets the default currency in the current user's settings |
|
||||
| `safeLowGasPrice` | `QVariant<string>` | `read` | `gasPricePredictionsChanged` | gets the current Ethereum networks's safe low gas price, in gwei |
|
||||
| `standardGasPrice` | `QVariant<string>` | `read` | `gasPricePredictionsChanged` | gets the current Ethereum networks's standard gas price, in gwei |
|
||||
| `fastGasPrice` | `QVariant<string>` | `read` | `gasPricePredictionsChanged` | gets the current Ethereum networks's fast gas price, in gwei |
|
||||
| `fastestGasPrice` | `QVariant<string>` | `read` | `gasPricePredictionsChanged` | gets the current Ethereum networks's fastest gas price, in gwei |
|
||||
| `fastestGasPrice` | `QVariant<string>` | `read` | none | gets the default gas limit for sending Ethereum, which is `"21000"` |
|
||||
| `defaultTokenList` | `QVariant<TokenList>` | `read` | none | gets the non-custom list of ERC-20 tokens for the currently selected wallet account |
|
||||
| `customTokenList` | `QVariant<TokenList>` | `read` | none | gets the custom list of ERC-20 tokens added by the user |
|
||||
| `dappBrowserAccount` | `QVariant<AccountItemView>` | `read` | `dappBrowserAccountChanged` | the wallet account currently used in the dapp browser |
|
||||
|
||||
### Methods
|
||||
Methods can be invoked by calling them directly on the `walletModel`, ie `walletModel.getSigningPhrase()`.
|
||||
|
||||
| Name | Params | Return type | Description |
|
||||
|-----------------------------------|---------|-----|-------------------------------------|
|
||||
| `getEtherscanLink` | none | `QVariant<string>`| Gets the link to Etherscan from the current network settings |
|
||||
| `getSigningPhrase` | none | `QVariant<string>`| Gets the link to Etherscan from the current network settings |
|
||||
| `getStatusToken` | none | `string`| Gets the Status token for the current network (ie SNT for mainnet and STT for Ropsten) and returns a stringified JSON object containing `name`, `symbol`, and `address` for the token. |
|
||||
| `getCurrentCollectiblesLists` | none | `QVariant<CollectiblesList>`| Gets the list of collectibles for the currently selected wallet. |
|
||||
| `getCurrentTransactions` | none | `QVariant<TransactionList>`| Gets the list of transactions for the currently selected wallet. |
|
||||
| `setCurrentAccountByIndex` | `index` (`int`): index of the account in the list of accounts | `void`| sets the currently selected account to the account at the provided index |
|
||||
| `getCurrentAccount` | none | `QVariant<AccountItemView>`| gets the currently selected account |
|
||||
| `setFocusedAccountByAddress` | `address` (`string`): address of the account to focus | `void`| sets the focused account in the chat transaction modal to the account with the provided address |
|
||||
| `getFocusedAccount` | none | `QVariant<AccountItemView>`| gets the currently focused account in the chat transaction modal |
|
||||
| `getCurrentAssetList` | none | `QVariant<AssetList>`| returns list of token assets for the currently selected wallet account |
|
||||
| `getTotalFiatBalance` | none | `QVariant<string>`| returns the total equivalent fiat balance of all wallets in the format `#.##` |
|
||||
| `getFiatValue` | `cryptoBalance` (`string`): balance whole (ie ETH)<br>`cryptoSymbol` (`string`): symbol to convert from<br>`fiatSymbol` (`string`) symbol of fiat currency to convert to | `QVariant<string>`| returns the total equivalent fiat balance in the format `#.##` |
|
||||
| `getCryptoValue` | `fiatBalance` (`string`): balance whole (ie USD)<br>`fiatSymbol` (`string`): fiat currency symbol to convert from<br>`cryptoSymbol` (`string`) symbol of fiat currency to convert to | `QVariant<string>`| returns the total equivalent crypto balance in the format `#.##` |
|
||||
| `getGasEthValue` | `gweiValue` (`string`): gas price in gwei<br>`gasLimit` (`string`): gas limit | `string` | gets maximum gas spend by multiplying the gas limit by the gas price |
|
||||
| `generateNewAccount` | `password` (`string`): password for the current user account<br>`accountName` (`string`): name for the new wallet account<br>`color` (`string`) hex code of the custom wallet color | `string` | creates a new account on the node with a custom name and color |
|
||||
| `addAccountsFromSeed` | `seed` (`string`): seed phrase of account<br>`password` (`string`): password of the current user account<br>`accountName` (`string`): name for the new wallet account<br>`color` (`string`) hex code of the custom wallet color | `string` | adds an account to the status-go node from the provided seed phrase |
|
||||
| `addAccountsFromPrivateKey` | `privateKey` (`string`): private key of account<br>`password` (`string`): password of the current user account<br>`accountName` (`string`): name for the new wallet account<br>`color` (`string`) hex code of the custom wallet color | `string` | adds an account to the status-go node from the provided private key |
|
||||
| `addWatchOnlyAccount` | `address` (`string`): address of account to watch<br>`accountName` (`string`): name for the new wallet account<br>`color` (`string`) hex code of the custom wallet color | `string` | watches an account without adding it to the status-go node |
|
||||
| `changeAccountSettings` | `address` (`string`): address of account<br>`accountName` (`string`): updated name for the wallet account<br>`color` (`string`) updated hex code of the account | `string` | updates the account's name and color |
|
||||
| `deleteAccount` | `address` (`string`): address of account | `string` | deletes an account from the status-go node and returns an error string or empty string if no error |
|
||||
| `getAccountList` | none | `QVariant<AccountList>` | returns list of accounts on status-go node |
|
||||
| `estimateGas` | `from_addr` (`string`): from address for the transaction<br>`to` (`string`): to address for the transaction<br>`assetAddress` (`string`): token contract address (use `"0x0000000000000000000000000000000000000000"` for Ethereum transactions)<br>`value` (`string`): amount of Ethereum to send (in whole Ethereum units)<br>`data` (`string`): encoded transaction data | `string` |returns a stringified JSON response from status with the transaction gas estimate and an `error` field if an error occurred. |
|
||||
| `transactionSent` | `txResult` (`string`): transaction result<br> | `void` | fires the QML signal `walletModel.transactionWasSent` |
|
||||
| `sendTransaction` | `from_addr` (`string`): from address for the transaction<br>`to` (`string`): to address for the transaction<br>`assetAddress` (`string`): token contract address (use `"0x0000000000000000000000000000000000000000"` for Ethereum transactions)<br>`value` (`string`): amount of Ethereum to send (in whole Ethereum units)<br>`gas` (`string`): gas to use for the transaction<br>`gas` (`string`): gas price for the transaction<br>`password` (`string`): password of the current user account<br>`uuid` (`string`): a unique identifier for the transaction request,so it can be idenified in QML when upon asynchronous completion<br> | `void` | sends a transaction in a separate thread. |
|
||||
| `getDefaultAccount` | none | `string` | returns the address of the currently selected account |
|
||||
| `defaultCurrency` | none | `string` | returns the currency symbol from settings |
|
||||
| `setDefaultCurrency` | `currency` (`string`): currency symbol, ie `"USD"` | `string` | set a new default currency in the current user's settings |
|
||||
| `hasAsset` | `account` (`string`): account to check for enabled token<br>`symbol` (`string`): token symbol, ie `"SNT"` | `bool` | returns true if token with `symbol` is enabled, false other wise |
|
||||
| `toggleAsset` | `symbol` (`string`): token symbol, ie `"SNT"` | `void` | enables a token with `symbol` or disables it it's already enabled |
|
||||
| `removeCustomToken` | `tokenAddress` (`string`): token contract address | `void` | removes the custom token from the list of tokens available in the wallet |
|
||||
| `addCustomToken` | `address` (`string`): token contract address<br>`name` (`string`): display name for the token<br>`symbol` (`string`): token symbol<br>`decimals` (`string`): number of decimals supported by the token | `void` | adds the custom token to the list of tokens available in the wallet |
|
||||
| `setCollectiblesResult` | `collectibleType` (`string`): `"cryptokitty"`, `"kudo"`, `"ethermon"`, `"stickers"` | `void` | sets the current wallet's collectibles |
|
||||
| `reloadCollectible` | `collectiblesJSON` (`string`): stringified JSON structure of collectibles | `void` | reloads the current wallet's collectibles in another thread |
|
||||
| `getGasPricePredictions` | none | `void` | gets current ethereum network gas predictions in a new thread |
|
||||
| `getGasPricePredictionsResult` | `gasPricePredictionsJson` (`string`): JSON stringified response of gas predictions | `void` | updates the current gas predictions for the ethereum network, and fires the `gasPricePredictionsChanged` signal |
|
||||
| `safeLowGasPrice` | none | `string` | returns the current Ethereum networks's safe low gas price, in gwei |
|
||||
| `standardGasPrice` | none | `string` | returns the current Ethereum networks's standard gas price, in gwei |
|
||||
| `fastGasPrice` | none | `string` | returns the current Ethereum networks's fast gas price, in gwei |
|
||||
| `fastestGasPrice` | none | `string` | returns the current Ethereum networks's fastest gas price, in gwei |
|
||||
| `defaultGasLimit` | none | `string` | returns the default gas limit for sending Ethereum, which is `"21000"` |
|
||||
| `getDefaultAddress` | none | `string` | returns the address of the first wallet account on the node |
|
||||
| `getDefaultTokenList` | none | `QVariant<TokenList>` | returns the non-custom list of ERC-20 tokens for the currently selected wallet account |
|
||||
| `loadCustomTokens` | none | none | loads the custom tokens in to the `TokenList` added by the user in to `walletModel.customTokenList` |
|
||||
| `getCustomTokenList` | none | `QVariant<TokenList>` | returns the custom list of ERC-20 tokens added by the user |
|
||||
| `isFetchingHistory` | `address` (`string`): address of the account to check | `bool` | returns `true` if `status-go` is currently fetching the transaction history for the specified account |
|
||||
| `isKnownTokenContract` | `address` (`string`): contract address | `bool` | returns `true` if the specified address is in the list of default or custom (user-added) contracts |
|
||||
| `decodeTokenApproval` | `tokenAddress` (`string`): contract address<br>`data` (`string`): response received from the ERC-20 token `Approve` function call | `string` | Returns stringified JSON result of the decoding. The JSON will contain only an `error` field if there was an error during decoding. Otherwise, it will contain a `symbol` (the token symbol) and an `amount` (amount approved to spend) field. |
|
||||
| `loadTransactionsForAccount` | `address` (`string`): address of the account to load transactions for | `void` | loads the transfer history for the specified account (result of `wallet_getTransfersByAddress`) in a separate thread |
|
||||
| `setTrxHistoryResult` | `historyJSON` (`string`): stringified JSON result from `status-go`'s response to `wallet_getTransfersByAddress` | `void` | sets the transaction history for the account requested. If the requested account was tracked by the `walletModel`, it will have its transactions updated (including `currentAccount`). The `loadingTrxHistoryChanged` signal is also fired with `false` as a parameter. |
|
||||
| `resolveENS` | `ens` (`string`): the ENS name to resolve | `void` | resolves an ENS name in a separate thread |
|
||||
| `ensResolved` | `ens` (`string`): the ENS name to resolve<br>`uuid` (`string`): a unique identifier to identify the request in QML so that only specific components can respond when needed | `void` | fires the `ensWasResolved` signal with the resolved address (`address`) and the unique identifier (`uuid`) |
|
||||
| `setDappBrowserAddress` | none | `void` | sets the dapp browser account to the account specified in settings and then fires the `dappBrowserAccountChanged` signal |
|
||||
| `getDappBrowserAccount` | none | `QVariant<AccountItemView>` | returns the wallet account currently used in the dapp browser |
|
||||
|
||||
#### AccountList
|
||||
`QAbstractListModel` to expose node accounts.
|
||||
The following roles are available to the model when bound to a QML control:
|
||||
|
||||
| Name | Description |
|
||||
|---------------|--------------|
|
||||
| `name` | account name defined by user |
|
||||
| `address` | account address |
|
||||
| `iconColor` | account color chosen by user |
|
||||
| `balance` | equivalent fiat balance for display, in format `$#.##` |
|
||||
| `fiatBalance` | the wallet's equivalent fiat balance in the format `#.##` (no currency as in `balance`) |
|
||||
| `assets` | returns an `AssetList` (see below) |
|
||||
| `isWallet` | flag indicating whether the asset is a token or a wallet |
|
||||
| `walletType` | in the case of a wallet, indicates the type of wallet ("key", "seed", "watch", "generated"). See `AccountItemView`for more information on wallet types. |
|
||||
|
||||
#### AssetList
|
||||
`QAbstractListModel` exposes ERC-20 token assets owned by a wallet account.
|
||||
The following roles are available to the model when bound to a QML control:
|
||||
|
||||
| Name | Description |
|
||||
|---------------|--------------|
|
||||
| `name` | token name |
|
||||
| `symbol` | token ticker symbol |
|
||||
| `value` | amount of token (in wei or equivalent) |
|
||||
| `fiatBalanceDisplay` | equivalent fiat balance for display, in format `$#.##` |
|
||||
| `address` | token contract address |
|
||||
| `fiatBalance` | equivalent fiat balance (not for display) |
|
||||
|
||||
#### CollectiblesList
|
||||
`QAbstractListModel` exposes ERC-721 assets for a wallet account.
|
||||
The following roles are available to the model when bound to a QML control:
|
||||
|
||||
| Name | Description |
|
||||
|---------------|--------------|
|
||||
| `collectibleType` | the type of collectible ("cryptokitty", "kudo", "ethermon", "stickers") |
|
||||
| `collectiblesJSON` | JSON representation of all collectibles in the list (schema is different for each type of collectible) |
|
||||
| `error` | error encountered while fetching the collectibles |
|
||||
|
||||
#### TransactionList
|
||||
`QAbstractListModel` to expose transactions for the currently selected wallet.
|
||||
The following roles are available to the model when bound to a QML control:
|
||||
|
||||
| Name | Description |
|
||||
|---------------|--------------|
|
||||
| `typeValue` | the transaction type |
|
||||
| `address` | ?? |
|
||||
| `blockNumber` | the block number the transaction was included in |
|
||||
| `blockHash` | the hash of the block |
|
||||
| `timestamp` | Unix timestamp of when the block was created |
|
||||
| `gasPrice` | gas price used in the transaction |
|
||||
| `gasLimit` | maximum gas allowed in this block |
|
||||
| `gasUsed` | amount of gas used in the transaction |
|
||||
| `nonce` | transaction nonce |
|
||||
| `txStatus` | transaction status |
|
||||
| `value` | value (in wei) of the transaction |
|
||||
| `fromAddress` | address the transaction was sent from |
|
||||
| `to` | address the transaction was sent to |
|
||||
| `contract` | ?? likely in a transfer transaction, the token contract interacted with |
|
||||
|
||||
#### AccountItemView
|
||||
This type can be accessed by any of the properties in the `walletModel` that return `QtObject<AccountItemView>`, ie `walletModel.currentAccount.name`. See the `walletModel`table above.
|
||||
|
||||
| Name | Type | Description |
|
||||
|---------------|----------|--------------|
|
||||
| `name*` | `string` | display name given to the wallet by the user |
|
||||
| `address*` | `string` | wallet's ethereum address |
|
||||
| `iconColor*` | `string` | wallet hexadecimal colour assigned to the wallet by the user |
|
||||
| `balance*` | `string` | the wallet's fiat balance used for display purposes in the format of `#.## USD` |
|
||||
| `fiatBalance*` | `string` | the wallet's equivalent fiat balance in the format `#.##` (no currency as in `balance`) |
|
||||
| `path*` | `string` | the wallet's HD derivation path |
|
||||
| `walletType*` | `string` | type determined by how the wallet was created. Values include: |
|
||||
| | | `"key"` - wallet was created with a private key |
|
||||
| | | `"seed"` - wallet was created with a seed phrase |
|
||||
| | | `"watch"` - wallet was created as to watch an Ethereum address (like a read-only wallet) |
|
||||
| | | `"generated"` - wallet was generated by the app |
|
||||
|
||||
#### TokenList
|
||||
`QAbstractListModel` exposes all displayable ERC-20 tokens.
|
||||
The following roles are available to the model when bound to a QML control:
|
||||
|
||||
| Name | Description |
|
||||
|---------------|--------------|
|
||||
| `name` | token display name |
|
||||
| `symbol` | token ticker symbol |
|
||||
| `hasIcon` | flag indicating whether or not the token has an icon |
|
||||
| `address` | the token's ERC-20 contract address |
|
||||
| `decimals` | the number of decimals held by the token |
|
||||
| `isCustom` | flag indicating whether the token was added by the user |
|
||||
|
||||
### Chats Model
|
||||
The wallet model (exposed as `chatsModel`) is used for functions pertaining to chatting.
|
||||
|
||||
#### Signals
|
||||
The `chatsModel` exposes the following signals, which can be consumed in QML using the `Connections` component (with a target of `chatsModel` and prefixed with `on`).
|
||||
| Name | Parameters | Description |
|
||||
|---------------|----------|--------------|
|
||||
| `oldestMessageTimestampChanged` | none | fired when the oldest message timestamp has changed |
|
||||
**WIP**
|
||||
|
||||
### QtProperties
|
||||
The following properties can be accessed directly on the `chatsModel`, ie `chatsModel.oldestMsgTimestamp`
|
||||
| Name | Type | Accessibility | Signal | Description |
|
||||
|---------------|------|---------------|--------|--------------|
|
||||
| `oldestMsgTimestamp` | `QVariant<int64>` | `read` | `oldestMessageTimestampChanged` | Gets the last set UNIX timestamp of the oldest message. See `setLastMessageTimestamp` for logic on how this is determined. |
|
||||
**WIP**
|
||||
|
||||
### Methods
|
||||
Methods can be invoked by calling them directly on the `chatsModel`, ie `chatsModel.getOldestMessageTimestamp()`.
|
||||
|
||||
| Name | Params | Return type | Description |
|
||||
|-----------------------------------|---------|-----|-------------------------------------|
|
||||
| `getOldestMessageTimestamp` | none | `QVariant<int64>`| Returns the last set UNIX timestamp of the oldest message. See `setLastMessageTimestamp` for logic on how this is determined. |
|
||||
**WIP**
|
||||
|
||||
#### ChannelsList
|
||||
`QAbstractListModel` to expose chat channels.
|
||||
The following roles are available to the model when bound to a QML control:
|
||||
|
||||
| Name | Description |
|
||||
|---------------|--------------|
|
||||
| `name` | name of the channel |
|
||||
**WIP**
|
||||
|
||||
*chatsModel.chats* - get channel list (list)
|
||||
|
||||
channel object:
|
||||
* name -
|
||||
* timestamp -
|
||||
* lastMessage.text -
|
||||
* unviewedMessagesCount -
|
||||
* identicon -
|
||||
* chatType -
|
||||
* color -
|
||||
|
||||
*chatsModel.activeChannelIndex* -
|
||||
*chatsModel.activeChannel* - return currently active channel (object)
|
||||
|
||||
active channel object:
|
||||
* id -
|
||||
* name -
|
||||
* color -
|
||||
* identicon -
|
||||
* chatType - (int)
|
||||
* members - (list)
|
||||
* userName
|
||||
* pubKey
|
||||
* isAdmin
|
||||
* joined
|
||||
* identicon
|
||||
* isMember(pubKey: string) - check if `pubkey` is a group member (bool)
|
||||
* isAdmin(pubKey: string) - check if `pubkey` is a group admin (bool)
|
||||
|
||||
*chatsModel.messageList* - returns messages for the current channel (list)
|
||||
|
||||
message object:
|
||||
* userName -
|
||||
* message -
|
||||
* timestamp -
|
||||
* clock -
|
||||
* identicon -
|
||||
* isCurrentUser -
|
||||
* contentType -
|
||||
* sticker -
|
||||
* fromAuthor -
|
||||
* chatId -
|
||||
* sectionIdentifier -
|
||||
* messageId -
|
||||
|
||||
*chatsModel.sendMessage(message: string)* - send a message to currently active channel
|
||||
|
||||
*chatsModel.joinChat(channel: string, chatTypeInt: int)* - join a channel
|
||||
|
||||
*chatsModel.groups.join()* - confirm joining group
|
||||
|
||||
*chatsModel.leaveActiveChat()* - leave currently active channel
|
||||
|
||||
*chatsModel.clearChatHistory()* - clear chat history of currently active channel
|
||||
|
||||
*chatsModel.groups.rename(newName: string)* - rename current active group
|
||||
|
||||
*chatsModel.blockContact(id: string)* - block contact
|
||||
|
||||
*chatsModel.addContact(id: string)*
|
||||
|
||||
*chatsModel.groups.create(groupName: string, pubKeys: string)*
|
||||
|
||||
**TODO**: document all exposed APIs to QML
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
### QML Crash course
|
||||
|
||||
**Intro**
|
||||
|
||||
Every QML file imports at least QtQuick and then other imports that might be required for the QML Types being used. QML Types have properties (similar to CSS somewhat), and typically contain other QML Types as children.
|
||||
|
||||
```qml
|
||||
import QtQuick 2.0
|
||||
import <SomeImportNeededForTypeName>
|
||||
|
||||
TypeName {
|
||||
propertyName: value
|
||||
|
||||
AnotherType {
|
||||
propertyName: value
|
||||
anotherProperty: value2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
example:
|
||||
|
||||
```qml
|
||||
import QtQuick 2.0
|
||||
|
||||
Rectangle {
|
||||
id: page
|
||||
width: 320; height: 480
|
||||
color: "lightgray"
|
||||
|
||||
Text {
|
||||
id: helloText
|
||||
text: "Hello world!"
|
||||
y: 30
|
||||
anchors.horizontalCenter: page.horizontalCenter
|
||||
font.pointSize: 24; font.bold: true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**QML Properties - using ids**
|
||||
|
||||
QML Types can be identified by an `id` which can be used as variable to access other properties from that element as parameter to other elements.
|
||||
|
||||
In this example, the `Text` element is identified with the id `tabBtnText`, we can use this to access the width of the text and used as value for the width of the Rectangle so its width is always the same as the text:
|
||||
|
||||
```qml
|
||||
Rectangle {
|
||||
id: tabButton
|
||||
width: tabBtnText.width // will always reflect the width of tabBtnText
|
||||
height: tabBtnText.height + 11
|
||||
|
||||
Text {
|
||||
id: tabBtnText
|
||||
text: "hello there"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Another example, combining a `TabBar` and a `StackLayout`, the StackLayout will display a different view depending on which TabButton has been selected since its index is taking the value of the tabbar
|
||||
|
||||
```qml
|
||||
TabBar {
|
||||
id: tabBar
|
||||
currentIndex: 0
|
||||
|
||||
TabButton { ... } // will change currentIndex to 0 if selected
|
||||
TabButton { ... } // will change currentIndex to 1 if selected
|
||||
...
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
...
|
||||
currentIndex: tabBar.currentIndex // use the newest value of the TabBar
|
||||
|
||||
Item {} // will be displayed if currentIndex == 0
|
||||
item {} // will be displayed if currentIndex == 1
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**QML Properties - parent and children**
|
||||
|
||||
It's possible to also refer to a `parent` of an element. This is typically used for widths & anchors but can be used to access any property from the parent, for example:
|
||||
|
||||
```qml
|
||||
ColumnLayout {
|
||||
id: suggestionsContainer
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Row {
|
||||
id: description
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 20
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or even a particular child using `children`, for example, here the rectangle remains at the width of the child text with an additional room of 10 pixels:
|
||||
|
||||
```qml
|
||||
Rectangle {
|
||||
width: children[0].width + 10
|
||||
|
||||
Text {
|
||||
text: "#" + channel
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**QML Types**
|
||||
A complete list of QML Types can be found in the QT documentation [here](https://doc.qt.io/qt-5/qmltypes.html)
|
||||
|
||||
some commonly used types in nim-status-client include:
|
||||
* [Text](https://doc.qt.io/qt-5/qml-qtquick-text.html)
|
||||
* [Image](https://doc.qt.io/qt-5/qml-qtquick-image.html)
|
||||
* SplitView
|
||||
* TabBar & TabButton
|
||||
* StackLayout
|
||||
* ColumnLayout & RowLayout
|
||||
* ListView
|
||||
|
||||
**SplitView Example**
|
||||
|
||||
The SplitView list items with a draggable splitter between each item
|
||||
|
||||
```qml
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.13 // required for SplitView
|
||||
|
||||
SplitView {
|
||||
id: walletView
|
||||
|
||||
// splitter settings
|
||||
handleDelegate: Rectangle {
|
||||
implicitWidth: 1
|
||||
implicitHeight: 4
|
||||
color: Theme.grey
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "item on the left"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "item on the right"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**TabBar & TabButton Example**
|
||||
|
||||
```qml
|
||||
TabBar {
|
||||
id: tabBar
|
||||
currentIndex: 0
|
||||
|
||||
TabButton { text: "foo" } // will change currentIndex to 0 if selected
|
||||
TabButton { text: "bar" } // will change currentIndex to 1 if selected
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
`tabBar.currentIndex` can then be used as value for some other property (typically used to supply the index for `StackLayout`)
|
|
@ -1,266 +0,0 @@
|
|||
## Communicating with NIM
|
||||
|
||||
**Using NimQml - General Overview**
|
||||
|
||||
Nim objects meant to be exposed to QT import `NimQml` and use the `QtObject` macro, there is some basic methods that need to be setup for every `QtObject` such as `setup`, `delete`, and when initializing the object the `new` and `setup` method need to be called.
|
||||
A basic QtObject will look like something like:
|
||||
|
||||
```nimrod=
|
||||
import NimQml
|
||||
|
||||
QtObject:
|
||||
type MyView* = ref object of QObject
|
||||
someField*: string
|
||||
|
||||
proc setup(self: MyView) =
|
||||
self.QObject.setup
|
||||
|
||||
proc delete*(self: MyView) =
|
||||
self.QObject.delete
|
||||
|
||||
proc newMyView*(): MyView =
|
||||
new(result, delete)
|
||||
result = MyView()
|
||||
result.setup
|
||||
```
|
||||
|
||||
The object then is exposed to QML by creating and registering a `QVariant`
|
||||
|
||||
```nimrod=
|
||||
import NimQml
|
||||
|
||||
...
|
||||
|
||||
# create variant
|
||||
var view = newMyView()
|
||||
var variant: QVariant = newQVariant(view)
|
||||
|
||||
# expose it to QML
|
||||
let engine = newQQmlApplicationEngine()
|
||||
engine.setRootContextProperty("MyNimObject", variant)
|
||||
```
|
||||
|
||||
The variable `MyNimObject` is then accessible in QML and represent `MyView` and its methods or variables that have been defined to be exposed to QML, for example, adding:
|
||||
|
||||
```qml
|
||||
proc foo*(self: MyView): string {.slot.} =
|
||||
"hello world"
|
||||
```
|
||||
|
||||
and in QML doing
|
||||
|
||||
```qml
|
||||
Text {
|
||||
text: "NIM says" + MyNimObject.foo()
|
||||
}
|
||||
```
|
||||
|
||||
will create a text "NIM says hello world"
|
||||
|
||||
**NimQml in nim-status-client**
|
||||
|
||||
The QtObjects are defined in `src/app/<module>/view.nim` and `src/app/<module>/views/`, they typically include the nim-status object as a parameter, for example `src/app/profile/view.nim`:
|
||||
|
||||
```nimrod=
|
||||
...
|
||||
QtObject:
|
||||
type ProfileView* = ref object of QObject
|
||||
...
|
||||
status*: Status
|
||||
|
||||
proc newProfileView*(status: Status): ProfileView =
|
||||
new(result, delete)
|
||||
result = ProfileView()
|
||||
...
|
||||
result.status = status
|
||||
result.setup
|
||||
...
|
||||
```
|
||||
|
||||
The variant is created and wrapped in the "controller" `src/app/<module>/core.nim`, for example `src/app/profile/core.nim`:
|
||||
|
||||
```nimrod=
|
||||
...
|
||||
type ProfileController* = ref object of SignalSubscriber
|
||||
view*: ProfileView
|
||||
variant*: QVariant
|
||||
status*: Status
|
||||
|
||||
proc newController*(status: Status): ProfileController =
|
||||
result = ProfileController()
|
||||
result.status = status
|
||||
result.view = newProfileView(status)
|
||||
result.variant = newQVariant(result.view)
|
||||
```
|
||||
|
||||
This controller is initialized in `src/nim_status_client.nim` and the variant is registered there, for example:
|
||||
|
||||
```nimrod=
|
||||
var profile = profile.newController(status)
|
||||
engine.setRootContextProperty("profileModel", profile.variant)
|
||||
```
|
||||
|
||||
this variant is then accessible in QML as `profileModel`, for example in `ui/app/AppLayouts/Profile/Sections/AboutContainer.qml` the node version is displayed with:
|
||||
|
||||
```qml
|
||||
...
|
||||
StyledText {
|
||||
text: qsTr("Node Version: %1").arg(profileModel.nodeVersion())
|
||||
...
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
**exposing methods to QML**
|
||||
|
||||
Methods can be exposed to QML need to be public and use the `{.slot.}` pragma
|
||||
|
||||
```nimrod=
|
||||
QtObject:
|
||||
...
|
||||
proc nodeVersion*(self: ProfileView): string {.slot.} =
|
||||
self.status.getNodeVersion()
|
||||
```
|
||||
|
||||
**QtProperty Macro for simple types**
|
||||
|
||||
There is a handy `QtProperty[type]` macro, this macro defines what methods to call to get the latest value (`read`), which method updates that value (`write`) and a signal that notifies that value has changed (`notify`), here is a real example from `src/app/wallet/view.nim` that defines the `defaultCurrency` property
|
||||
|
||||
```nimrod=
|
||||
proc defaultCurrency*(self: WalletView): string {.slot.} =
|
||||
self.status.wallet.getDefaultCurrency()
|
||||
|
||||
proc defaultCurrencyChanged*(self: WalletView) {.signal.}
|
||||
|
||||
proc setDefaultCurrency*(self: WalletView, currency: string) {.slot.} =
|
||||
self.status.wallet.setDefaultCurrency(currency)
|
||||
self.defaultCurrencyChanged() # notify value has changed
|
||||
|
||||
QtProperty[string] defaultCurrency:
|
||||
read = defaultCurrency
|
||||
write = setDefaultCurrency
|
||||
notify = defaultCurrencyChange
|
||||
```
|
||||
|
||||
note: it's not necessary to define all these fields except for `read`
|
||||
|
||||
**QtProperty Macro for other QObjects**
|
||||
|
||||
This macro can also be used to expose other QtObjects as QVariants, this is typically done to simplify code and sometimes even required for things that need to be their own individual QTObjects such as Lists.
|
||||
|
||||
For example, in `src/app/profile/view.nim` the profileView QtObject (`src/app/profile/profileView.nim`) is exposed to QML with:
|
||||
|
||||
```nimrod=
|
||||
QtObject:
|
||||
type ProfileView* = ref object of QObject
|
||||
profile*: ProfileInfoView
|
||||
...
|
||||
...
|
||||
proc getProfile(self: ProfileView): QVariant {.slot.} =
|
||||
return newQVariant(self.profile)
|
||||
|
||||
proc setNewProfile*(self: ProfileView, profile: Profile) =
|
||||
self.profile.setProfile(profile)
|
||||
|
||||
QtProperty[QVariant] profile:
|
||||
read = getProfile
|
||||
```
|
||||
|
||||
**QAbstractListModel**
|
||||
|
||||
Lists are exposed to QML using a `QAbstractListModel` object, this method expects certain methods to be defined so QML can access the data: `rowCount`, `data` and `roleNames`
|
||||
Other methods can be found in the QT documentation [here](https://doc.qt.io/qt-5/qabstractitemmodel.html)
|
||||
|
||||
Let's take as an example `src/app/wallet/views/asset_list.nim`
|
||||
|
||||
First the imports
|
||||
|
||||
```nim
|
||||
import NimQml
|
||||
import tables
|
||||
```
|
||||
|
||||
then we define the `QtObject` macro as usual but this time the object uses `QAbstractListModel`
|
||||
|
||||
```nim
|
||||
QtObject:
|
||||
type AssetList* = ref object of QAbstractListModel
|
||||
assets*: seq[Asset]
|
||||
```
|
||||
|
||||
`assets` is the sequence that will hold the assets, `Asset` is imported and defined in `src/status/wallet/accounts.nim` and is a simple nim object
|
||||
|
||||
```nimrod=
|
||||
type Asset* = ref object
|
||||
name*, symbol*, value*, fiatValue*, accountAddress*, address*: string
|
||||
```
|
||||
|
||||
then there is the typical required initialization
|
||||
|
||||
```nimrod=
|
||||
proc setup(self: AssetList) = self.QAbstractListModel.setup
|
||||
|
||||
proc delete(self: AssetList) =
|
||||
self.QAbstractListModel.delete
|
||||
self.assets = @[]
|
||||
|
||||
proc newAssetList*(): AssetList =
|
||||
new(result, delete)
|
||||
result.assets = @[]
|
||||
result.setup
|
||||
```
|
||||
|
||||
a role enum type needs to be defined, specifying the name of each field
|
||||
|
||||
```nimrod=
|
||||
type
|
||||
AssetRoles {.pure.} = enum
|
||||
Name = UserRole + 1,
|
||||
Symbol = UserRole + 2,
|
||||
Value = UserRole + 3,
|
||||
FiatValue = UserRole +
|
||||
```
|
||||
|
||||
for the data to be exposed there are methods that need to be defined such as `rowCount` and `data`:
|
||||
|
||||
```nimrod=
|
||||
# returns total assets
|
||||
method rowCount(self: AssetList, index: QModelIndex = nil): int =
|
||||
return self.assets.len
|
||||
|
||||
# returns Asset object at given index
|
||||
method data(self: AssetList, index: QModelIndex, role: int): QVariant =
|
||||
if not index.isValid:
|
||||
return
|
||||
if index.row < 0 or index.row >= self.assets.len:
|
||||
return
|
||||
let asset = self.assets[index.row]
|
||||
let assetRole = role.AssetRoles
|
||||
case assetRole:
|
||||
of AssetRoles.Name: result = newQVariant(asset.name)
|
||||
of AssetRoles.Symbol: result = newQVariant(asset.symbol)
|
||||
of AssetRoles.Value: result = newQVariant(asset.value)
|
||||
of AssetRoles.FiatValue: result = newQVariant(asset.fiatValue)
|
||||
|
||||
# returns table with columns names and values
|
||||
method roleNames(self: AssetList): Table[int, string] =
|
||||
{ AssetRoles.Name.int:"name",
|
||||
AssetRoles.Symbol.int:"symbol",
|
||||
AssetRoles.Value.int:"value",
|
||||
AssetRoles.FiatValue.int:"fiatValue" }.toTable
|
||||
```
|
||||
|
||||
The asset list has been exposed in `src/app/wallet/view.nim` as QVariant called `assets` and the table can be display in QML, for example using a `ListView`:
|
||||
|
||||
```qml
|
||||
ListView {
|
||||
model: walletModel.assets // the table
|
||||
delegate: Text {
|
||||
text: "name:" + name + " | symbol: " + symbol
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**TODO**: qml components with default properties
|
||||
**TODO**: reusable qml components
|
||||
**TODO**: qml components alias properties
|
|
@ -1,96 +0,0 @@
|
|||
## Structure
|
||||
|
||||
**stack**
|
||||
|
||||
* 1. status-go (`src/status/libstatus`)
|
||||
* 2. nim-status / business logic & persistence (`src/status`)
|
||||
* 3. initializer wrapper (`src/app/<module>/core.nim`)
|
||||
* currently contains signals which should be moved into layer 2.
|
||||
* 4. views & view logic (`src/app/<module>/view.nim` & `ui/*.qml`)
|
||||
|
||||
**folder structure**
|
||||
|
||||
`src/` - where most of the source is
|
||||
`src/app` - Where the Application is
|
||||
`src/app/<module>` - module e.g 'chat', 'profile'
|
||||
`src/app/<module>/core.nim` - wrapper for this module
|
||||
`src/app/<module>/view.nim` - view, exposed data and some view specific logic
|
||||
`src/app/<module>/views/*` - views
|
||||
`src/signals` - signals (should be refactored & moved into src/status)
|
||||
`src/status` - business logic
|
||||
`src/status/libstatus` - integration with status-go
|
||||
`nim_status_client.nim` - the main file
|
||||
`ui/` - QML files
|
||||
|
||||
**`src/status`**
|
||||
|
||||
This folder contains the library that abstracts the status app business logic, it's how the app can interact with status-go as well as how they can obtain data, do actions etc..
|
||||
|
||||
* this folder can only import / call files from `src/status/libstatus` (exception for libraries ofc)
|
||||
* only files in `app/` should be able to import from `src/status` (but never `src/status/libstatus`)
|
||||
|
||||
**`src/status/libstatus`**
|
||||
|
||||
This folder abstracts the interactions with status-go
|
||||
|
||||
* generally this folder should only contain code related to interacting with status-go
|
||||
* it should not import code from anywhere else (including `src/status`)
|
||||
* nothing should call libstatus directly
|
||||
* only the code in `status/` should be able to import / call files in `src/status/libstatus`
|
||||
|
||||
**`src/app`**
|
||||
|
||||
This folder contains the code related to each section of the app, generally it should be kept to a minimum amount of logic, *it knows what to do, but not how to do it*
|
||||
|
||||
**`src/app/<module>/`**
|
||||
|
||||
* each `<module>` folder inside `app/` should correspond to a section in the app (exception for the `onboarding/` and `login/` currently)
|
||||
* there should be no new folders here unless we are adding a brand new section to the sidebar
|
||||
* files inside a `<module>` should not import files from another `<module>`
|
||||
* while the code here can react to events emited by nim-status (`src/status`) it should not be able to emit events
|
||||
|
||||
**`src/app/<module>/core.nim`**
|
||||
|
||||
This file is the controller of this module, the general structure of controller is typically predictable and always the same
|
||||
|
||||
* it imports a view
|
||||
* it imports the nim-status lib
|
||||
* it contains an `init` method
|
||||
* it exposes a QVariant
|
||||
|
||||
the constructor has typically the following structure
|
||||
|
||||
```nimrod=
|
||||
type NodeController* = ref object of SignalSubscriber
|
||||
status*: Status
|
||||
view*: NodeView
|
||||
variant*: QVariant
|
||||
|
||||
proc newController*(status: Status): NodeController =
|
||||
result = NodeController()
|
||||
result.status = status
|
||||
result.view = newNodeView(status)
|
||||
result.variant = newQVariant(result.view)
|
||||
|
||||
method onSignal(self: NodeController, data: Signal) =
|
||||
var msg = cast[WalletSignal](data)
|
||||
# Do something with the signal...
|
||||
```
|
||||
|
||||
* with the exception of `src/status/` and its own files within `src/app/<module>` (i.e the views), a controller should **not** import files from anywhere else (including other files inside `app/`)
|
||||
|
||||
**`src/app/<module>/view.nim`**
|
||||
|
||||
This file contains the main QtObject for this `<module>` and exposes methods to interact with the views for the controller and QML.
|
||||
|
||||
* this file cannot import any other file except:
|
||||
* other views within this `<module>`
|
||||
* `src/status/` to use their types
|
||||
* if there are multiple subviews, then they should go into the `views/` folder and initialized in this file.
|
||||
|
||||
## Future directions
|
||||
|
||||
* signals will be refactored/moved from core.nim files and `signals/` into `src/status/` and instead handle as events
|
||||
* instead of importing `src/status/libstatus` in `src/status` files, we will do dependency injection, this allow us to more easily do unit tests, as well as transition from status-go to nimstatus
|
||||
* `src/status` should be reanamed to `src/nim-status`
|
||||
* `src/status/libstatus` should be renamed to `src/nim-status/status-go`
|
106
docs/tips.md
106
docs/tips.md
|
@ -1,106 +0,0 @@
|
|||
## tips and tricks
|
||||
|
||||
### seeing output of macros
|
||||
|
||||
```nimrod=
|
||||
import macros
|
||||
|
||||
expandmacros:
|
||||
#code
|
||||
```
|
||||
|
||||
then during compilation it will display what the expanded code looks like
|
||||
|
||||
### Getting notified for QML properties changing
|
||||
|
||||
Each QML property has an `onChange` attached to it automatically.
|
||||
|
||||
For example, if you a property named `name`, it will have an `onNameChanged`. It follows the pattern: `on` + Property + `Change`.
|
||||
|
||||
Eg:
|
||||
```
|
||||
property int index: 0
|
||||
|
||||
onIndexChanged: {
|
||||
console.log('Index changed', index)
|
||||
}
|
||||
```
|
||||
|
||||
## Async
|
||||
```nimrod
|
||||
import chronos
|
||||
|
||||
proc someFunction*(someParameter:int): Future[string] {.async.}=
|
||||
result = "Something"
|
||||
|
||||
|
||||
var myResult = waitFor someFunction(1)
|
||||
|
||||
# If inside some async function
|
||||
var myResult = await someFunction(6464435)
|
||||
|
||||
# to discard the result,
|
||||
asyncCheck someFunction(2332)
|
||||
|
||||
```
|
||||
|
||||
`nim-chronos` API is compatible with https://nim-lang.org/docs/asyncdispatch.html so this page can be used to complement nim-chronos lack of documentation. ([Wiki](https://github.com/status-im/nim-chronos/wiki/AsyncDispatch-comparison))
|
||||
|
||||
## Updating data on a QAbstractListModel
|
||||
While adding/removing values from a list is easy, updating the values of a list requires some extra manipulation:
|
||||
```
|
||||
proc updateRecord(idx: int, newValue: string) =
|
||||
self.myList[idx] = newValue;
|
||||
var topLeft = self.createIndex(idx,0,nil)
|
||||
var bottomRight = self.createIndex(idx,0,nil)
|
||||
self.dataChanged(topLeft, bottomRight, @[RoleNames.SomeRole.int, RoleNames.AnotherRole.int, RoleNames.SomeOtherRole.int])
|
||||
```
|
||||
|
||||
If more than one record is being updated at once, change the `topLeft`'s and `bottomRight`'s `self.createIndex` first parameter to indicate the initial row number and the final row number that were affected.
|
||||
|
||||
To refresh the whole table, I think you can use `0` as the first parameter for `createIndex` for both `topLeft` and `bottomRight`.
|
||||
|
||||
The final attribute of `dataChanged` is a non-empty sequence of RoleNames containing the attributes that were updated
|
||||
|
||||
## Error Handling and Custom Errors
|
||||
|
||||
### Raising Custom errors
|
||||
|
||||
```nim
|
||||
type
|
||||
CustomError* = object of Exception # CatchableError/Defect
|
||||
|
||||
try:
|
||||
raise newException(CustomError, "Some error message")
|
||||
except CustomError as e:
|
||||
echo e.msg
|
||||
```
|
||||
|
||||
### Raising Custom Errors with custom data (and parent Error)
|
||||
|
||||
```nim
|
||||
type
|
||||
CustomError* = object of Exception
|
||||
customField*: string
|
||||
|
||||
type CustomErrorRef = ref CustomError
|
||||
|
||||
try:
|
||||
raise CustomErrorRef(msg: "Some error message", customField: "Some custom error data", parent: (ref ValueError)(msg: "foo bar"))
|
||||
except CustomError as e:
|
||||
echo e.msg & ": " & e.customField
|
||||
echo "Original: " & e.parent.msg
|
||||
```
|
||||
|
||||
### Implementing custom Error helpers with default values
|
||||
|
||||
```nim
|
||||
type
|
||||
CustomError* = object of Exception
|
||||
customField*: string
|
||||
|
||||
type CustomErrorRef = ref CustomError
|
||||
|
||||
proc newCustomError*(customData: string): CustomErrorRef =
|
||||
result = CustomErrorRef(msg: "This is some custom error message", customField: customData, parent: (ref ValueError)(msg: "Value error"))
|
||||
```
|
|
@ -1,73 +0,0 @@
|
|||
## Adding a sidebar section
|
||||
|
||||
The sidebar and each section is defined at `AppMain.qml`, it contains
|
||||
|
||||
* sidebar - `TabBar` with `TabButton` elements
|
||||
* main section - `StackLayout`
|
||||
|
||||
The currently displayed section in the `StackLayout` is determined by the `currentIndex` property, for example `0` will show the first child (in this case `ChatLayout`), `1` will show the second child, and so on
|
||||
|
||||
This property is being defined by whatever is the currently selected button in the `Tabbar` with `currentIndex: tabBar.currentIndex`
|
||||
|
||||
```qml
|
||||
TabBar {
|
||||
id: tabBar
|
||||
|
||||
TabButton { ... }
|
||||
TabButton { ... }
|
||||
...
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
...
|
||||
currentIndex: tabBar.currentIndex
|
||||
|
||||
ChatLayout {}
|
||||
WalletLayout {}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
To add a new section, then add a new TabButton to the TabBar, for example:
|
||||
|
||||
```qml
|
||||
TabBar {
|
||||
...
|
||||
TabButton {
|
||||
id: myButton
|
||||
visible: this.enabled
|
||||
width: 40
|
||||
height: this.enabled ? 40 : 0
|
||||
text: ""
|
||||
anchors.topMargin: this.enabled ? 50 : 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: nodeBtn.top // needs to be previous button
|
||||
background: Rectangle {
|
||||
color: Theme.lightBlue
|
||||
opacity: parent.checked ? 1 : 0
|
||||
radius: 50
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: parent.checked ? "img/node.svg" : "img/node.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then a section to the StackLayout
|
||||
|
||||
```qml
|
||||
StackLayout {
|
||||
...
|
||||
|
||||
Text {
|
||||
text: "hello world!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The section can be any qml element, to create your own custom element, see the next section
|
|
@ -1,72 +0,0 @@
|
|||
## Creating a Custom QML component
|
||||
|
||||
Creating a custom element typically involves
|
||||
* creating a new QML file
|
||||
* adding that QML file in a qmldir file
|
||||
* adding that QML file to the project `nim-status-client.pro` file (automatic if done in QT Creator)
|
||||
|
||||
The easiest way is to do it in QT creator although this can be done manually as well, if not using QT Creator make sure the files are added in the nim-status-client.pro.pro file.
|
||||
|
||||
**step 1 - create folder**
|
||||
|
||||
In QT creator, go to `app/AppLayouts` right click, and select "New folder", name the folder `MySection`
|
||||
|
||||
**step 2 - create QML file**
|
||||
|
||||
In `MySection`, right click, and select "Add New", select "QT" -> "QML File (Qt Quick 2)", as a name put `MyQMLComponent.qml` and create the file.
|
||||
|
||||
Add the desired content, for example
|
||||
|
||||
```qml
|
||||
import QtQuick 2.0
|
||||
|
||||
Item {
|
||||
Text {
|
||||
text: "hello"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
if not using QT Creator, make sure the files are added in the nim-status-client.pro.pro file, for e.g:
|
||||
|
||||
```
|
||||
DISTFILES += \
|
||||
app/AppLayouts/MySection/MyQMLComponent.qml \
|
||||
```
|
||||
|
||||
**step 3 - add the component to qmldir**
|
||||
|
||||
In `app/AppLayouts/` edit `qmldir` and add the file
|
||||
|
||||
```
|
||||
BrowserLayout 1.0 Browser/BrowserLayout.qml
|
||||
ChatLayout 1.0 Chat/ChatLayout.qml
|
||||
NodeLayout 1.0 Node/NodeLayout.qml
|
||||
ProfileLayout 1.0 Profile/ProfileLayout.qml
|
||||
WalletLayout 1.0 Wallet/WalletLayout.qml
|
||||
|
||||
MyQMLComponent 1.0 MySection/MyQMLComponent.qml
|
||||
```
|
||||
|
||||
This ensures that when `app/AppLayouts/` is imported, the component `MyQMLComponent` will point to the component at `MySection/MyQMLComponent.qml`
|
||||
|
||||
**step 4 - use the component**
|
||||
|
||||
Note that `AppMain.qml` already imports AppLayouts
|
||||
|
||||
```qml
|
||||
import "./AppLayouts"
|
||||
```
|
||||
|
||||
which makes the `MyQMLComponent` available
|
||||
|
||||
In the section created in the `Adding a sidebar section`, replace it with this component
|
||||
|
||||
```qml
|
||||
StackLayout {
|
||||
...
|
||||
|
||||
MyQMLComponent {
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,59 +0,0 @@
|
|||
# Description
|
||||
|
||||
This document describes how the signing of Windows application was configured.
|
||||
|
||||
# Certificates
|
||||
|
||||
The signing uses two types of Certificates:
|
||||
|
||||
* Self-Signed Code Signing certificate for development and PR builds
|
||||
* [DigiCert](https://www.digicert.com/) standard release Code Signing certificate
|
||||
|
||||
## Self-Signed Certificate
|
||||
|
||||
This certificate was created on using the following PowerShell commands:
|
||||
```Powershell
|
||||
$cert = New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname status.im -Subject "Dev Status Cert" -type CodeSigning
|
||||
$pwd = ConvertTo-SecureString -String 'SUPER-SECRET-PASSWORD -Force -AsPlainText
|
||||
Export-PfxCertificate -cert $cert -FilePath Status-Destkop-SelfSigned.pfx -Password $pwd -CryptoAlgorithmOption AES256_SHA256
|
||||
```
|
||||
Which should create a `Status-Destkop-SelfSigned.pfx` file encrypted with the provided password.
|
||||
|
||||
Keep in mind that the `-type CodeSigning` flag is important.
|
||||
|
||||
For more details see [this article](http://woshub.com/how-to-create-self-signed-certificate-with-powershell/).
|
||||
|
||||
## DigiCert Certificate
|
||||
|
||||
This certificate is was purchased on 23rd of September 2020 from [DigiCert.com](https://www.digicert.com/).
|
||||
It is a `Microsoft Authenticode` certificate and should be valid for 2 years.
|
||||
|
||||
# Continuous Integration
|
||||
|
||||
The Jenkins setup which makes use of these certificates makes them available under different job folder under different credential names. This way we can sign non-release builds while not making them appear to a user as a release build. The self-signed certificate should trigger windows warnings when starting the application.
|
||||
|
||||
The way this works is certificates are split across two Jenkins job folders:
|
||||
|
||||
* [status-desktop/platforms](https://ci.status.im/job/status-desktop/job/platforms/credentials/store/folder/domain/_/) - Release and Nightly builds.
|
||||
* [status-desktop/branches](https://ci.status.im/job/status-desktop/job/branches/credentials/store/folder/domain/_/) - Branch and PR builds.
|
||||
|
||||
These folders contain different certificates, which provides another layer of security in case someone submits a malicious PR which attempts to extract valuable secrets. In this setup the only thing they might possibly extract would be the self-signed certificate and its password.
|
||||
|
||||
The exact access to the credentials is hidden from malicious eyes that can inspect `Jenkinsfile`s in this repo, and instead are implemented in our private [`status-jenkins-lib`](https://github.com/status-im/status-jenkins-lib) repository under `vars/windows.groovy`.
|
||||
|
||||
# Known Issues
|
||||
|
||||
#### `Error: Store::ImportCertObject() failed. (-2146893808/0x80090010)`
|
||||
|
||||
This error would appears when trying to sign binaries with `signtool.exe` when Jenkins was accessing the Windows CI slave via SSH.
|
||||
|
||||
The solution was to switch the setup to deploy the [Jenkins Remoting Agent Service](https://www.jenkins.io/projects/remoting/) using the [WinSW](https://github.com/winsw/winsw) utility to run it as a Windows service.
|
||||
|
||||
#### `CertEnroll::CX509Enrollment::_CreateRequest: Access denied. 0x80090010 (-2146893808 NTE_PERM)`
|
||||
|
||||
You cannot create a self-signed certificate in a PowerShell instance without elevated privilidges. You might have to run the shell as system administrator.
|
||||
|
||||
# Links
|
||||
|
||||
* https://github.com/status-im/infra-ci/issues/28
|
||||
* https://github.com/status-im/status-desktop/issues/2170
|
Before Width: | Height: | Size: 466 KiB After Width: | Height: | Size: 466 KiB |
Loading…
Reference in New Issue