feat: show terms and conditions, add ens registry contract, and determine if you have enough SNT balance.

This commit is contained in:
Richard Ramos 2020-08-26 17:13:26 -04:00 committed by Iuri Matias
parent fef2e6651d
commit ce8cd4183f
9 changed files with 355 additions and 16 deletions

View File

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

View File

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

View File

@ -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 = %* [{
@ -111,3 +116,50 @@ proc address*(username: string): string =
if address == "0x0000000000000000000000000000000000000000000000000000000000000000":
return ""
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

View File

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

View File

@ -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 =

View File

@ -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,

View File

@ -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)

View File

@ -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: 24
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: 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()
}
}

View File

@ -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();
}
}