feat: show terms and conditions, add ens registry contract, and determine if you have enough SNT balance.
This commit is contained in:
parent
fef2e6651d
commit
ce8cd4183f
|
@ -6,7 +6,10 @@ import sequtils
|
|||
from ../../../status/libstatus/types import Setting
|
||||
import ../../../status/threads
|
||||
import ../../../status/ens as status_ens
|
||||
import ../../../status/libstatus/wallet as status_wallet
|
||||
import ../../../status/libstatus/settings as status_settings
|
||||
import ../../../status/libstatus/utils as utils
|
||||
import ../../../status/libstatus/tokens as tokens
|
||||
import ../../../status/status
|
||||
|
||||
type
|
||||
|
@ -130,3 +133,12 @@ QtObject:
|
|||
{
|
||||
EnsRoles.UserName.int:"username"
|
||||
}.toTable
|
||||
|
||||
proc getPrice(self: EnsManager): string {.slot.} =
|
||||
result = utils.wei2Eth(getPrice())
|
||||
|
||||
proc getUsernameRegistrar(self: EnsManager): string {.slot.} =
|
||||
result = statusRegistrarAddress()
|
||||
|
||||
proc getENSRegistry(self: EnsManager): string {.slot.} =
|
||||
result = registry
|
||||
|
|
|
@ -2,6 +2,7 @@ import NimQml, Tables, strformat, strutils, chronicles, json, std/wrapnils, pars
|
|||
import ../../status/[status, wallet, threads]
|
||||
import ../../status/wallet/collectibles as status_collectibles
|
||||
import ../../status/libstatus/wallet as status_wallet
|
||||
import ../../status/libstatus/tokens
|
||||
import ../../status/libstatus/utils
|
||||
import views/[asset_list, account_list, account_item, transaction_list, collectibles_list]
|
||||
|
||||
|
@ -430,3 +431,9 @@ QtObject:
|
|||
QtProperty[string] defaultGasLimit:
|
||||
read = defaultGasLimit
|
||||
|
||||
proc getSNTBalance*(self: WalletView): string {.slot.} =
|
||||
let currAcct = status_wallet.getWalletAccounts()[0]
|
||||
result = getSNTBalance($currAcct.address)
|
||||
|
||||
proc getDefaultAddress*(self: WalletView): string {.slot.} =
|
||||
result = $status_wallet.getWalletAccounts()[0].address
|
||||
|
|
|
@ -2,12 +2,17 @@ import strutils
|
|||
import profile/profile
|
||||
import nimcrypto
|
||||
import json
|
||||
import json_serialization
|
||||
import tables
|
||||
import strformat
|
||||
import libstatus/core
|
||||
import libstatus/types
|
||||
import libstatus/utils
|
||||
import stew/byteutils
|
||||
import unicode
|
||||
import algorithm
|
||||
|
||||
import eth/common/eth_types, stew/byteutils
|
||||
import libstatus/contracts
|
||||
const domain* = ".stateofus.eth"
|
||||
|
||||
proc userName*(ensName: string, removeSuffix: bool = false): string =
|
||||
|
@ -46,7 +51,7 @@ proc namehash*(ensName:string): string =
|
|||
|
||||
result = "0x" & node.toHex()
|
||||
|
||||
const registry = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
|
||||
const registry* = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
|
||||
const resolver_signature = "0x0178b8bf"
|
||||
proc resolver*(usernameHash: string): string =
|
||||
let payload = %* [{
|
||||
|
@ -110,4 +115,51 @@ proc address*(username: string): string =
|
|||
let address = response.parseJson["result"].getStr;
|
||||
if address == "0x0000000000000000000000000000000000000000000000000000000000000000":
|
||||
return ""
|
||||
result = "0x" & address.substr(26)
|
||||
result = "0x" & address.substr(26)
|
||||
|
||||
proc getPrice*(): Stuint[256] =
|
||||
let
|
||||
contract = contracts.getContract("ens-usernames")
|
||||
payload = %* [{
|
||||
"to": $contract.address,
|
||||
"data": contract.methods["getPrice"].encodeAbi()
|
||||
}, "latest"]
|
||||
|
||||
let responseStr = callPrivateRPC("eth_call", payload)
|
||||
let response = Json.decode(responseStr, RpcResponse)
|
||||
if not response.error.isNil:
|
||||
raise newException(RpcException, "Error getting ens username price: " & response.error.message)
|
||||
if response.result == "0x":
|
||||
raise newException(RpcException, "Error getting ens username price: 0x")
|
||||
result = fromHex(Stuint[256], response.result)
|
||||
|
||||
proc registerUsername*(username:string, address: EthAddress, pubKey: string, price: Stuint[256], password: string): string =
|
||||
let label = fromHex(FixedBytes[32], namehash(addDomain(username)))
|
||||
let x = fromHex(FixedBytes[32], "0x" & pubkey[4..67])
|
||||
let y = fromHex(FixedBytes[32], "0x" & pubkey[68..131])
|
||||
let
|
||||
ensUsernamesContract = contracts.getContract("ens-usernames")
|
||||
sntContract = contracts.getContract("snt")
|
||||
price = getPrice()
|
||||
register = Register(label: label, account: address, x: x, y: y)
|
||||
registerAbiEncoded = ensUsernamesContract.methods["register"].encodeAbi(register)
|
||||
|
||||
let
|
||||
approveAndCallObj = ApproveAndCall(to: ensUsernamesContract.address, value: price, data: DynamicBytes[136].fromHex(registerAbiEncoded))
|
||||
approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi(approveAndCallObj)
|
||||
|
||||
let payload = %* {
|
||||
"from": $address,
|
||||
"to": $sntContract.address,
|
||||
# "gas": 200000, # TODO: obtain gas price?
|
||||
"data": approveAndCallAbiEncoded
|
||||
}
|
||||
|
||||
let responseStr = sendTransaction($payload, password)
|
||||
let response = Json.decode(responseStr, RpcResponse)
|
||||
if not response.error.isNil:
|
||||
raise newException(RpcException, "Error registering ens-username: " & response.error.message)
|
||||
result = response.result # should be a tx receipt
|
||||
|
||||
proc statusRegistrarAddress*():string =
|
||||
result = $contracts.getContract("ens-usernames").address
|
||||
|
|
|
@ -26,10 +26,17 @@ type
|
|||
address*: EthAddress
|
||||
price*: Stuint[256]
|
||||
|
||||
Register* = object
|
||||
label*: FixedBytes[32]
|
||||
account*: EthAddress
|
||||
x*: FixedBytes[32]
|
||||
y*: FixedBytes[32]
|
||||
|
||||
|
||||
ApproveAndCall* = object
|
||||
to*: EthAddress
|
||||
value*: Stuint[256]
|
||||
data*: DynamicBytes[100]
|
||||
data*: DynamicBytes[136]
|
||||
|
||||
Transfer* = object
|
||||
to*: EthAddress
|
||||
|
|
|
@ -4,8 +4,8 @@ from eth/common/utils import parseAddress
|
|||
import ./types, ./settings, ./coder
|
||||
|
||||
export
|
||||
GetPackData, PackData, BuyToken, ApproveAndCall, Transfer, BalanceOf,
|
||||
TokenOfOwnerByIndex, TokenPackId, TokenUri, DynamicBytes, toHex, fromHex
|
||||
GetPackData, PackData, BuyToken, ApproveAndCall, Transfer, BalanceOf, Register,
|
||||
TokenOfOwnerByIndex, TokenPackId, TokenUri, FixedBytes, DynamicBytes, toHex, fromHex
|
||||
|
||||
type Method* = object
|
||||
name*: string
|
||||
|
@ -90,6 +90,18 @@ proc allContracts(): seq[Contract] = @[
|
|||
].toTable
|
||||
),
|
||||
Contract(name: "crypto-kitties", network: Network.Mainnet, address: parseAddress("0x06012c8cf97bead5deae237070f9587f8e7a266d")),
|
||||
Contract(name: "ens-usernames", network: Network.Mainnet, address: parseAddress("0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"),
|
||||
methods: [
|
||||
("register", Method(signature: "register(bytes32,address,bytes32,bytes32)")),
|
||||
("getPrice", Method(signature: "getPrice()"))
|
||||
].toTable
|
||||
),
|
||||
Contract(name: "ens-usernames", network: Network.Testnet, address: parseAddress("0x11d9F481effd20D76cEE832559bd9Aca25405841"),
|
||||
methods: [
|
||||
("register", Method(signature: "register(bytes32,address,bytes32,bytes32)")),
|
||||
("getPrice", Method(signature: "getPrice()"))
|
||||
].toTable
|
||||
),
|
||||
]
|
||||
|
||||
proc getContract(network: Network, name: string): Contract =
|
||||
|
|
|
@ -134,7 +134,7 @@ proc buyPack*(packId: Stuint[256], address: EthAddress, price: Stuint[256], pass
|
|||
buyToken = BuyToken(packId: packId, address: address, price: price)
|
||||
buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(buyToken)
|
||||
let
|
||||
approveAndCallObj = ApproveAndCall(to: stickerMktContract.address, value: price, data: DynamicBytes[100].fromHex(buyTxAbiEncoded))
|
||||
approveAndCallObj = ApproveAndCall(to: stickerMktContract.address, value: price, data: DynamicBytes[136].fromHex(buyTxAbiEncoded))
|
||||
approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi(approveAndCallObj)
|
||||
let payload = %* {
|
||||
"from": $address,
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import json, chronicles, strformat, stint, strutils
|
||||
import core, wallet
|
||||
import contracts
|
||||
import eth/common/eth_types, eth/common/utils, stew/byteutils
|
||||
import json_serialization
|
||||
|
||||
logScope:
|
||||
topics = "wallet"
|
||||
|
@ -36,6 +39,10 @@ proc getTokenBalance*(tokenAddress: string, account: string): string =
|
|||
let balance = response.parseJson["result"].getStr
|
||||
result = $hex2Eth(balance)
|
||||
|
||||
proc getSNTBalance*(account: string): string =
|
||||
let snt = contracts.getContract("snt")
|
||||
result = getTokenBalance("0x" & $snt.address, account)
|
||||
|
||||
proc addOrRemoveToken*(enable: bool, address: string, name: string, symbol: string, decimals: int, color: string): JsonNode =
|
||||
if enable:
|
||||
addCustomToken(address, name, symbol, decimals, color)
|
||||
|
|
|
@ -7,6 +7,10 @@ import "../../../../../shared"
|
|||
Item {
|
||||
property var onClick: function(){}
|
||||
|
||||
property string username: ""
|
||||
|
||||
signal backBtnClicked();
|
||||
|
||||
StyledText {
|
||||
id: sectionTitle
|
||||
//% "ENS usernames"
|
||||
|
@ -19,6 +23,124 @@ Item {
|
|||
font.pixelSize: 20
|
||||
}
|
||||
|
||||
ModalPopup {
|
||||
id: popup
|
||||
title: qsTr("Terms of name registration")
|
||||
|
||||
ScrollView {
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
spacing: Style.current.halfPadding
|
||||
height: childrenRect.height
|
||||
width: parent.width
|
||||
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Funds are deposited for 1 year. Your SNT will be locked, but not spent.")
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("After 1 year, you can release the name and get your deposit back, or take no action to keep the name.")
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("If terms of the contract change — e.g. Status makes contract upgrades — user has the right to release the username regardless of time held.")
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("The contract controller cannot access your deposited funds. They can only be moved back to the address that sent them.")
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Your address(es) will be publicly associated with your ENS name.")
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Usernames are created as subdomain nodes of stateofus.eth and are subject to the ENS smart contract terms.")
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("You authorize the contract to transfer SNT on your behalf. This can only occur when you approve a transaction to authorize the transfer.")
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("These terms are guaranteed by the smart contract logic at addresses:")
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("%1 (Status UsernameRegistrar).").arg(profileModel.ens.getUsernameRegistrar())
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
font.family: Style.current.fontHexRegular.name
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr(`<a href="%1%2">Look up on Etherscan</a>`).arg(walletModel.etherscanLink.replace("/tx", "/address")).arg(profileModel.ens.getUsernameRegistrar())
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("%1 (ENS Registry).").arg(profileModel.ens.getENSRegistry())
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
font.family: Style.current.fontHexRegular.name
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr(`<a href="%1%2">Look up on Etherscan</a>`).arg(walletModel.etherscanLink.replace("/tx", "/address")).arg(profileModel.ens.getENSRegistry())
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: sview
|
||||
clip: true
|
||||
|
@ -36,19 +158,131 @@ Item {
|
|||
anchors.right: parent.right;
|
||||
anchors.left: parent.left;
|
||||
|
||||
StyledText {
|
||||
id: title
|
||||
//% "TODO: show T&C and confirmation screen for acquiring a ens username"
|
||||
text: qsTrId("todo--show-t-c-and-confirmation-screen-for-acquiring-a-ens-username")
|
||||
Rectangle {
|
||||
id: circleAt
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 24
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 60
|
||||
height: 60
|
||||
radius: 120
|
||||
color: Style.current.blue
|
||||
|
||||
StyledText {
|
||||
text: "@"
|
||||
opacity: 0.7
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: 18
|
||||
color: Style.current.white
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: ensUsername
|
||||
text: username + ".stateofus.eth"
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: 24
|
||||
font.pixelSize: 18
|
||||
anchors.top: circleAt.bottom
|
||||
anchors.topMargin: 24
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
TextWithLabel {
|
||||
id: walletAddressLbl
|
||||
label: qsTr("Wallet address")
|
||||
text: walletModel.getDefaultAddress()
|
||||
textToCopy: profileModel.profile.address
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 24
|
||||
anchors.top: ensUsername.bottom
|
||||
anchors.topMargin: 24
|
||||
}
|
||||
|
||||
TextWithLabel {
|
||||
id: keyLbl
|
||||
label: qsTr("Key")
|
||||
text: {
|
||||
let pubKey = profileModel.profile.pubKey;
|
||||
return pubKey.substring(0, 20) + "..." + pubKey.substring(pubKey.length - 20);
|
||||
}
|
||||
textToCopy: profileModel.profile.pubKey
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 24
|
||||
anchors.top: walletAddressLbl.bottom
|
||||
anchors.topMargin: 24
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: termsAndConditionsCheckbox
|
||||
anchors.top: keyLbl.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 24
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Agree to <a href=\"#\">Terms of name registration.</a> I understand that my wallet address will be publicly connected to my username.")
|
||||
anchors.left: termsAndConditionsCheckbox.right
|
||||
anchors.right: parent.right
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.top: termsAndConditionsCheckbox.top
|
||||
onLinkActivated: popup.open()
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton // we don't want to eat clicks on the Text
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.current.padding
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
label: qsTr("Back")
|
||||
onClicked: backBtnClicked()
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.top: startBtn.top
|
||||
anchors.right: startBtn.left
|
||||
anchors.rightMargin: Style.current.padding
|
||||
width: childrenRect.width
|
||||
|
||||
Image {
|
||||
id: image1
|
||||
height: 50
|
||||
width: 50
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../../../../../shared/img/status-logo.png"
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: ensPriceLbl
|
||||
text: qsTr("10 SNT")
|
||||
anchors.left: image1.right
|
||||
anchors.leftMargin: 5
|
||||
anchors.top: image1.top
|
||||
color: Style.current.textColor
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Deposit")
|
||||
anchors.left: image1.right
|
||||
anchors.leftMargin: 5
|
||||
anchors.topMargin: 5
|
||||
anchors.top: ensPriceLbl.bottom
|
||||
color: Style.current.secondaryText
|
||||
font.pixelSize: 14
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,9 +290,10 @@ Item {
|
|||
id: startBtn
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.current.padding
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
//% "Ok"
|
||||
label: qsTrId("ok")
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
label: parseFloat(walletModel.getSNTBalance()) < 10 ? qsTr("Not enough SNT") : qsTr("Ok")
|
||||
disabled: parseFloat(walletModel.getSNTBalance()) < 10 || !termsAndConditionsCheckbox.checked
|
||||
onClicked: onClick()
|
||||
}
|
||||
}
|
|
@ -145,6 +145,10 @@ Item {
|
|||
targetState: welcomeState
|
||||
signal: goToWelcome
|
||||
}
|
||||
DSM.SignalTransition {
|
||||
targetState: listState
|
||||
signal: back
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,6 +171,7 @@ Item {
|
|||
if(output === "connected"){
|
||||
connect(username)
|
||||
} else {
|
||||
selectedUsername = username;
|
||||
next(output);
|
||||
}
|
||||
}
|
||||
|
@ -176,9 +181,11 @@ Item {
|
|||
Component {
|
||||
id: termsAndConditions
|
||||
TermsAndConditions {
|
||||
username: selectedUsername
|
||||
onClick: function(output){
|
||||
next(output);
|
||||
}
|
||||
onBackBtnClicked: back();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue