mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-26 22:51:14 +00:00
feat(apps/token): add token definition prototype
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(TokenUiPlugin LANGUAGES CXX)
|
||||
|
||||
find_package(Qt6 6.8 REQUIRED COMPONENTS Core Gui Network Qml Quick QuickControls2)
|
||||
qt_standard_project_setup(REQUIRES 6.8)
|
||||
|
||||
include(CTest)
|
||||
|
||||
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
|
||||
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
|
||||
else()
|
||||
message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.")
|
||||
endif()
|
||||
|
||||
set(LOGOS_WALLET_SOURCE_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../shared/wallet"
|
||||
CACHE PATH "Path to the shared Logos wallet module"
|
||||
)
|
||||
set(LOGOS_WALLET_GENERATED_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/generated_code"
|
||||
CACHE PATH "Path to generated Logos SDK sources"
|
||||
)
|
||||
add_subdirectory("${LOGOS_WALLET_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/shared-wallet")
|
||||
|
||||
# ui_qml module with a hand-written C++ backend (QtRO .rep view contract +
|
||||
# generated *SimpleSource/*ViewPluginBase). Mirrors the LEZ wallet UI module.
|
||||
# The token prototype owns no wallet implementation; it links the repository's
|
||||
# shared wallet access library and consumes its compiled Logos.Wallet QML module.
|
||||
logos_module(
|
||||
NAME token_ui
|
||||
REP_FILE src/TokenUiBackend.rep
|
||||
SOURCES
|
||||
src/TokenUiPluginInterface.h
|
||||
src/TokenUiPlugin.h
|
||||
src/TokenUiPlugin.cpp
|
||||
src/TokenUiBackend.h
|
||||
src/TokenUiBackend.cpp
|
||||
FIND_PACKAGES
|
||||
Qt6Gui
|
||||
LINK_LIBRARIES
|
||||
Qt6::Gui
|
||||
LINK_TARGETS
|
||||
logos_wallet_access
|
||||
)
|
||||
@@ -0,0 +1,89 @@
|
||||
# Token UI
|
||||
|
||||
A QML UI application for the token program.
|
||||
|
||||
See the [Logos QML UI App Tutorial](https://github.com/logos-co/logos-tutorial/blob/master/tutorial-qml-ui-app.md) for more information.
|
||||
|
||||
> **Status:** interactive UI prototype. The **Create** view models the current
|
||||
> Token Program's fungible and non-fungible definition settings, and **Inspect**
|
||||
> renders the supplied July 12, 2026 testnet fixture snapshot. It never creates
|
||||
> accounts, signs, reads a live chain, or submits a transaction.
|
||||
|
||||
## Token-definition prototype
|
||||
|
||||
The prototype is deliberately local-only. It lets an operator prepare every
|
||||
currently supported creation shape and see the resulting stored state before a
|
||||
production transaction path exists:
|
||||
|
||||
- Fungible definitions: raw `u128` supply; fixed (`None`), self, or external
|
||||
mint authority; metadata omitted or linked.
|
||||
- Non-fungible definitions: printable supply plus required metadata, with an
|
||||
initial master holding that controls printing.
|
||||
- Metadata: `Simple` or `Expanded` standard, URI, and creators string. The
|
||||
program initializes `primary_sale_date` to `0`; it has no creation input for
|
||||
decimals, symbol, description, image, royalties, collection, or mutable
|
||||
metadata.
|
||||
- Target accounts: definition, first holding/master, and metadata (when used)
|
||||
are surfaced because each must be fresh and authorized at submission time.
|
||||
|
||||
The **Inspect** fixtures are a historical testnet reference, not a live read.
|
||||
They include fixed, self-authorized, external-authority, revoked-authority, and
|
||||
metadata-backed fungibles, plus the Glitchlings NFT collection. Display decimals
|
||||
shown for fungibles are UI inference from the fixture policy; the Token Program
|
||||
does not store a decimal field.
|
||||
|
||||
Metadata-backed definitions and NFT definitions need typed instruction
|
||||
serialization today because the generic IDL cannot encode the structured
|
||||
creation arguments. Any production submission implementation must use that
|
||||
route rather than treating this prototype as a transaction client.
|
||||
|
||||
## Wallet / chain integration
|
||||
|
||||
This app is a `ui_qml` module with a hand-written C++ backend
|
||||
(`src/TokenUiBackend.*`, plugin in `src/TokenUiPlugin.*`) that depends on the
|
||||
core **`logos_execution_zone`** wallet module. The backend delegates wallet
|
||||
lifecycle and account state to the repository's shared wallet access library
|
||||
(`apps/shared/wallet`) and exposes an async QtRO surface
|
||||
(`src/TokenUiBackend.rep`) plus its shared account list model to the QML view.
|
||||
The navbar consumes the compiled `Logos.Wallet` QML module.
|
||||
|
||||
**Onboarding is non-invasive.** The app opens straight to the first screen; the
|
||||
navbar shows **Connect** (opens a password-only modal) or **Connected** + the
|
||||
account selector. There is no path picking — the wallet uses LEZ's canonical
|
||||
home, `~/.lee/wallet/` (override with `LEE_WALLET_HOME_DIR`, the same var LEZ
|
||||
honors), and its config (`wallet_config.json`) self-initializes.
|
||||
|
||||
Account/keystore sharing follows the runtime:
|
||||
|
||||
- **Standalone** (`nix run .`): own core-module instance, but the canonical
|
||||
`~/.lee/wallet` keystore is shared with the LEZ wallet UI and any other LEZ
|
||||
app on the machine. A previously-created wallet auto-opens on launch.
|
||||
- **Inside Basecamp**: the core wallet module is a single shared instance, so on
|
||||
startup the shared wallet provider reuses the already-open wallet, surfacing
|
||||
**shared** accounts across apps.
|
||||
|
||||
## Setup
|
||||
|
||||
This project requires Nix with experimental features enabled. If you haven't already, enable them permanently:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/nix && echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
|
||||
```
|
||||
|
||||
## Running the UI
|
||||
|
||||
Start the UI with:
|
||||
|
||||
```bash
|
||||
nix run .
|
||||
```
|
||||
|
||||
This builds and runs the application in development mode.
|
||||
|
||||
## Updating Dependencies
|
||||
|
||||
To update the pinned versions of dependencies in `flake.lock`:
|
||||
|
||||
```bash
|
||||
nix flake update
|
||||
```
|
||||
Generated
+26869
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
{
|
||||
description = "Logos Token QML UI — create and manage tokens on the LEZ token program";
|
||||
|
||||
inputs = {
|
||||
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
||||
|
||||
# Shared C++ wallet access and Logos.Wallet QML sources.
|
||||
shared_wallet = {
|
||||
url = "path:../shared/wallet";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
# Core wallet module (the LEZ wallet FFI Qt plugin). The input name must
|
||||
# match the metadata.json `dependencies` entry so the builder can resolve
|
||||
# it as a module dependency. Keep this aligned with the AMM UI module.
|
||||
logos_execution_zone = {
|
||||
url = "github:logos-blockchain/logos-execution-zone-module?rev=d70225ced646934d2294fd9e8f8b03615c104b80";
|
||||
inputs.logos-execution-zone.url =
|
||||
"github:logos-blockchain/logos-execution-zone?rev=a7e06a660940a00093b1760560d37ff84aff5a05";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = inputs@{ logos-module-builder, shared_wallet, ... }:
|
||||
logos-module-builder.lib.mkLogosQmlModule {
|
||||
src = ./.;
|
||||
configFile = ./metadata.json;
|
||||
flakeInputs = inputs;
|
||||
preConfigure = ''
|
||||
cmakeFlagsArray+=("-DLOGOS_WALLET_SOURCE_DIR=${shared_wallet}")
|
||||
'';
|
||||
externalLibInputs = { };
|
||||
postInstall = ''
|
||||
test -f ${./qml}/Logos/Wallet/qmldir
|
||||
|
||||
walletQmlDir="shared-wallet/qml/Logos/Wallet"
|
||||
if [ ! -d "$walletQmlDir" ]; then
|
||||
echo "Built Logos.Wallet QML module not found"
|
||||
exit 1
|
||||
fi
|
||||
walletQmlInstallDir="$out/lib/Logos/Wallet"
|
||||
mkdir -p "$walletQmlInstallDir"
|
||||
cp -r "$walletQmlDir/." "$walletQmlInstallDir/"
|
||||
test -f "$walletQmlInstallDir/qmldir"
|
||||
'';
|
||||
};
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "token_ui",
|
||||
"version": "0.1.0",
|
||||
"type": "ui_qml",
|
||||
"category": "token",
|
||||
"description": "UI module for the token program",
|
||||
"main": "token_ui_plugin",
|
||||
"view": "qml/Main.qml",
|
||||
"icon": "icons/token.png",
|
||||
"dependencies": ["logos_execution_zone"],
|
||||
|
||||
"nix": {
|
||||
"packages": {
|
||||
"build": [],
|
||||
"runtime": ["qt6.qtdeclarative", "zstd", "krb5", "abseil-cpp"]
|
||||
},
|
||||
"external_libraries": [],
|
||||
"cmake": {
|
||||
"find_packages": [],
|
||||
"extra_sources": [],
|
||||
"extra_include_dirs": [],
|
||||
"extra_link_libraries": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module Logos.Wallet
|
||||
optional plugin logos_wallet_qmlplugin ../../../Logos/Wallet
|
||||
classname Logos_WalletPlugin
|
||||
prefer :/qt/qml/Logos/Wallet/
|
||||
depends QtQuick
|
||||
@@ -0,0 +1,103 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.Theme
|
||||
|
||||
import "pages"
|
||||
import "state"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Backend replica + account model, bridged from the C++ backend.
|
||||
readonly property var backend: logos.module("token_ui")
|
||||
readonly property var accountModel: logos.model("token_ui", "accountModel")
|
||||
|
||||
property bool ready: false
|
||||
|
||||
// Local-only prototype data. It is seeded from the supplied testnet
|
||||
// deployment record and never performs a chain read or submits a call.
|
||||
TokenPrototypeStore {
|
||||
id: tokenPrototypeStore
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: logos
|
||||
function onViewModuleReadyChanged(moduleName, isReady) {
|
||||
if (moduleName === "token_ui")
|
||||
root.ready = isReady && root.backend !== null;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.ready = root.backend !== null && logos.isViewModuleReady("token_ui");
|
||||
}
|
||||
|
||||
// Connectivity banner: shown when a wallet is open but its configured
|
||||
// sequencer doesn't answer reachability probes (so transactions will fail).
|
||||
Rectangle {
|
||||
id: connectionBanner
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
z: 101
|
||||
|
||||
readonly property bool show: root.ready && root.backend && root.backend.isWalletOpen && root.backend.sequencerAddr.length > 0 && !root.backend.sequencerReachable
|
||||
|
||||
height: show ? 32 : 0
|
||||
visible: height > 0
|
||||
clip: true
|
||||
color: Theme.palette.warning
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 40
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideMiddle
|
||||
font.pixelSize: 12
|
||||
font.weight: Font.Medium
|
||||
color: Theme.palette.background
|
||||
text: qsTr("Unable to connect to network")
|
||||
}
|
||||
}
|
||||
|
||||
// The app is always usable; the wallet is opt-in via the navbar "Connect"
|
||||
// control. Prototype views render immediately and stay local-only.
|
||||
NavBar {
|
||||
id: navbar
|
||||
anchors.top: connectionBanner.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
z: 100
|
||||
|
||||
backend: root.ready ? root.backend : null
|
||||
accountModel: root.accountModel
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
anchors.top: navbar.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
currentIndex: navbar.currentIndex
|
||||
|
||||
CreatePage {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
store: tokenPrototypeStore
|
||||
}
|
||||
|
||||
ManagePage {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
store: tokenPrototypeStore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.Controls
|
||||
import Logos.Theme
|
||||
import Logos.Wallet
|
||||
|
||||
// Self-contained navigation bar — styling is independent of any view's theme.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property alias currentIndex: navigationTabs.currentIndex
|
||||
|
||||
// Wallet wiring, passed down from Main.qml.
|
||||
property var backend: null
|
||||
property var accountModel: null
|
||||
|
||||
implicitHeight: 56
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.palette.background
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 1
|
||||
color: Theme.palette.borderSecondary
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 20
|
||||
anchors.rightMargin: 20
|
||||
spacing: 4
|
||||
|
||||
// App identity
|
||||
Text {
|
||||
text: qsTr("Logos Token")
|
||||
color: Theme.palette.text
|
||||
font.pixelSize: 17
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
LogosTabBar {
|
||||
id: navigationTabs
|
||||
|
||||
Layout.preferredWidth: 180
|
||||
|
||||
LogosTabButton {
|
||||
text: qsTr("Create")
|
||||
Accessible.name: qsTr("Open Create")
|
||||
}
|
||||
|
||||
LogosTabButton {
|
||||
text: qsTr("Inspect")
|
||||
Accessible.name: qsTr("Open Inspect")
|
||||
}
|
||||
}
|
||||
|
||||
// Wallet / account control on the far right.
|
||||
WalletControl {
|
||||
id: accountControl
|
||||
Layout.leftMargin: 12
|
||||
wallet: root.backend
|
||||
accountModel: root.accountModel
|
||||
viewportWidth: root.width
|
||||
watchCall: function(result, success, failure) {
|
||||
logos.watch(result, success, failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,990 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.Controls
|
||||
import Logos.Theme
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var store: null
|
||||
property int tokenKind: 0
|
||||
property int authorityMode: 0
|
||||
property bool metadataEnabled: false
|
||||
property int metadataStandard: 0
|
||||
property bool prepared: false
|
||||
property string preparedMessage: ""
|
||||
|
||||
readonly property bool isFungible: tokenKind === 0
|
||||
readonly property bool requiresMetadata: !isFungible
|
||||
readonly property bool hasMetadata: requiresMetadata || metadataEnabled
|
||||
readonly property string maximumU128: "340282366920938463463374607431768211455"
|
||||
readonly property string instructionName: isFungible && !hasMetadata ? "new_fungible_definition" : "new_definition_with_metadata"
|
||||
readonly property string supplyLabel: isFungible ? qsTr("Initial raw supply") : qsTr("Printable supply")
|
||||
readonly property string supplyValue: supplyField.text.length > 0 ? supplyField.text : qsTr("Not set")
|
||||
readonly property string authorityValue: !isFungible ? qsTr("Not applicable to NFT definitions") : authorityMode === 0 ? qsTr("None — fixed supply") : authorityMode === 1 ? qsTr("Definition account — self authority") : externalAuthorityField.text.length > 0 ? externalAuthorityField.text : qsTr("External authority required")
|
||||
readonly property bool validSupply: isUnsignedU128(supplyField.text)
|
||||
readonly property bool validDefinitionTarget: isAccountId(definitionTargetField.text)
|
||||
readonly property bool validHoldingTarget: isAccountId(holdingTargetField.text)
|
||||
readonly property bool validMetadataTarget: !hasMetadata || isAccountId(metadataTargetField.text)
|
||||
readonly property bool validExternalAuthority: !isFungible || authorityMode !== 2 || (isAccountId(externalAuthorityField.text) && externalAuthorityField.text !== "11111111111111111111111111111111")
|
||||
readonly property bool canPrepare: validSupply && validDefinitionTarget && validHoldingTarget && validMetadataTarget && validExternalAuthority
|
||||
readonly property var authorityOptions: [
|
||||
{ text: qsTr("Fixed supply — no future minting"), accessibleName: qsTr("Fixed supply with no mint authority") },
|
||||
{ text: qsTr("Self authority — definition account signs future mints"), accessibleName: qsTr("Use definition account as mint authority") },
|
||||
{ text: qsTr("External authority — a separate account signs future mints"), accessibleName: qsTr("Use external mint authority") }
|
||||
]
|
||||
readonly property var metadataStandards: [
|
||||
{ text: qsTr("Simple"), accessibleName: qsTr("Use Simple metadata standard") },
|
||||
{ text: qsTr("Expanded"), accessibleName: qsTr("Use Expanded metadata standard") }
|
||||
]
|
||||
readonly property var examplePatterns: [
|
||||
{ pattern: "fixed", text: qsTr("Fixed example"), accessibleName: qsTr("Load fixed supply creation example") },
|
||||
{ pattern: "metadata", text: qsTr("Metadata example"), accessibleName: qsTr("Load metadata token creation example") },
|
||||
{ pattern: "nft", text: qsTr("NFT example"), accessibleName: qsTr("Load non-fungible collection creation example") }
|
||||
]
|
||||
readonly property var readinessChecks: [
|
||||
{ label: qsTr("Definition target"), detail: qsTr("init · signer · writable"), ok: validDefinitionTarget },
|
||||
{ label: isFungible ? qsTr("Initial holding") : qsTr("NFT master holding"), detail: qsTr("init · signer · writable"), ok: validHoldingTarget },
|
||||
{ label: qsTr("Metadata target"), detail: hasMetadata ? qsTr("init · signer · writable") : qsTr("not used by this instruction"), ok: hasMetadata ? validMetadataTarget : true }
|
||||
].concat(isFungible && authorityMode === 2 ? [
|
||||
{ label: qsTr("External mint authority"), detail: qsTr("Must be a non-zero account ID"), ok: validExternalAuthority }
|
||||
] : [])
|
||||
|
||||
component FormTextField: LogosTextField {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 42
|
||||
textInput.selectByMouse: true
|
||||
}
|
||||
|
||||
component CompactButton: LogosButton {
|
||||
Layout.preferredWidth: 132
|
||||
Layout.preferredHeight: 34
|
||||
radius: Theme.spacing.radiusMedium
|
||||
}
|
||||
|
||||
function isUnsignedU128(value) {
|
||||
if (!/^[0-9]+$/.test(value))
|
||||
return false;
|
||||
|
||||
var normalized = value.replace(/^0+(?=\d)/, "");
|
||||
if (normalized.length < maximumU128.length)
|
||||
return true;
|
||||
if (normalized.length > maximumU128.length)
|
||||
return false;
|
||||
return normalized <= maximumU128;
|
||||
}
|
||||
|
||||
function isAccountId(value) {
|
||||
return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value);
|
||||
}
|
||||
|
||||
function setPattern(pattern) {
|
||||
prepared = false;
|
||||
preparedMessage = "";
|
||||
|
||||
if (pattern === "fixed") {
|
||||
tokenKind = 0;
|
||||
authorityMode = 0;
|
||||
metadataEnabled = false;
|
||||
nameField.text = qsTr("Example fixed supply");
|
||||
supplyField.text = "7654321";
|
||||
return;
|
||||
}
|
||||
|
||||
if (pattern === "metadata") {
|
||||
tokenKind = 0;
|
||||
authorityMode = 2;
|
||||
metadataEnabled = true;
|
||||
metadataStandard = 1;
|
||||
nameField.text = qsTr("Example metadata token");
|
||||
supplyField.text = "10000000000000000000000000";
|
||||
metadataUriField.text = "data:application/json;base64,...";
|
||||
creatorsField.text = qsTr("Creator or authority label");
|
||||
return;
|
||||
}
|
||||
|
||||
tokenKind = 1;
|
||||
metadataEnabled = true;
|
||||
metadataStandard = 1;
|
||||
nameField.text = qsTr("Example collection");
|
||||
supplyField.text = "64";
|
||||
metadataUriField.text = "data:application/json;base64,...";
|
||||
creatorsField.text = qsTr("Master-holding creator");
|
||||
}
|
||||
|
||||
function prepareDefinition() {
|
||||
if (!canPrepare)
|
||||
return;
|
||||
|
||||
var draft = {
|
||||
id: "draft-" + Date.now(),
|
||||
name: nameField.text.length > 0 ? nameField.text : qsTr("Untitled definition"),
|
||||
type: isFungible ? "fungible" : "nonFungible",
|
||||
definitionId: definitionTargetField.text,
|
||||
holdingId: holdingTargetField.text,
|
||||
metadataId: hasMetadata ? metadataTargetField.text : "",
|
||||
rawSupply: supplyField.text,
|
||||
displaySupply: supplyField.text,
|
||||
inferredDecimals: "",
|
||||
authorityMode: isFungible ? (authorityMode === 0 ? "fixed" : authorityMode === 1 ? "self" : "external") : "masterHolding",
|
||||
authority: isFungible && authorityMode === 2 ? externalAuthorityField.text : authorityMode === 1 ? definitionTargetField.text : "",
|
||||
metadataStandard: hasMetadata ? (metadataStandard === 0 ? "Simple" : "Expanded") : "",
|
||||
metadataUri: hasMetadata ? metadataUriField.text : "",
|
||||
creators: hasMetadata ? creatorsField.text : "",
|
||||
description: qsTr("Prepared locally. No token account or transaction was created."),
|
||||
source: "draft",
|
||||
instruction: instructionName,
|
||||
printableCopies: !isFungible ? supplyField.text : ""
|
||||
};
|
||||
|
||||
if (store && store.addDraft)
|
||||
store.addDraft(draft);
|
||||
|
||||
prepared = true;
|
||||
preparedMessage = qsTr("Draft prepared locally. Switch to Inspect to review it alongside the testnet fixtures.");
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#151515"
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: scroll
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentWidth: width
|
||||
contentHeight: content.implicitHeight + 48
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
|
||||
width: Math.max(280, Math.min(scroll.width - 40, 1440))
|
||||
x: Math.max(20, (scroll.width - width) / 2)
|
||||
y: 24
|
||||
spacing: 18
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 18
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 28
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Create token definition")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 14
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("Prepare the exact definition shape first. This prototype does not create accounts, sign, or submit a transaction.")
|
||||
}
|
||||
}
|
||||
|
||||
LogosBadge {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
color: Theme.palette.primary
|
||||
text: qsTr("Prototype")
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
id: workbench
|
||||
|
||||
readonly property int columnCount: content.width >= 1220 ? 3 : content.width >= 820 ? 2 : 1
|
||||
|
||||
Layout.fillWidth: true
|
||||
columns: columnCount
|
||||
columnSpacing: 14
|
||||
rowSpacing: 14
|
||||
|
||||
Rectangle {
|
||||
id: formPanel
|
||||
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: workbench.columnCount === 3 ? 520 : 0
|
||||
Layout.columnSpan: workbench.columnCount === 1 ? 1 : 1
|
||||
implicitHeight: formContent.implicitHeight + 32
|
||||
radius: 16
|
||||
color: "#1B1B1B"
|
||||
border.color: "#303030"
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: formContent
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 14
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Definition settings")
|
||||
}
|
||||
|
||||
LogosTabBar {
|
||||
id: kindTabs
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 42
|
||||
currentIndex: root.tokenKind
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
root.tokenKind = currentIndex;
|
||||
if (root.requiresMetadata)
|
||||
root.metadataEnabled = true;
|
||||
root.prepared = false;
|
||||
}
|
||||
|
||||
LogosTabButton {
|
||||
text: qsTr("Fungible")
|
||||
Accessible.name: qsTr("Create fungible token definition")
|
||||
}
|
||||
|
||||
LogosTabButton {
|
||||
text: qsTr("Non-fungible")
|
||||
Accessible.name: qsTr("Create non-fungible token definition")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 13
|
||||
wrapMode: Text.Wrap
|
||||
text: root.isFungible ? qsTr("Fungible definitions create one initial holding with the full raw supply.") : qsTr("NFT definitions always include metadata and create a master holding that controls printing.")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: "#303030"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 7
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 15
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Identity and supply")
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Name")
|
||||
}
|
||||
|
||||
FormTextField {
|
||||
id: nameField
|
||||
|
||||
Accessible.name: qsTr("Token definition name")
|
||||
placeholderText: qsTr("Name stored on the definition")
|
||||
text: qsTr("New token")
|
||||
onTextChanged: root.prepared = false
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: root.supplyLabel
|
||||
}
|
||||
|
||||
FormTextField {
|
||||
id: supplyField
|
||||
|
||||
Accessible.name: root.supplyLabel
|
||||
placeholderText: qsTr("Unsigned 128-bit integer")
|
||||
text: "1000000"
|
||||
textInput.inputMethodHints: Qt.ImhDigitsOnly
|
||||
textInput.validator: RegularExpressionValidator {
|
||||
regularExpression: /^[0-9]*$/
|
||||
}
|
||||
onTextChanged: root.prepared = false
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: root.validSupply ? "#A9A098" : "#F08A76"
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
text: root.validSupply ? qsTr("Raw u128 value. Decimal display precision is not stored by the Token Program.") : qsTr("Enter an unsigned 128-bit integer (0 through 340282366920938463463374607431768211455).")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
color: "#303030"
|
||||
visible: root.isFungible
|
||||
Layout.preferredHeight: visible ? 1 : 0
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: root.isFungible
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
spacing: 7
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 15
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Mint authority")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("This is the full authority surface at creation. NFTs have no definition-level mint authority.")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.authorityOptions
|
||||
|
||||
delegate: LogosRadioButton {
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
Accessible.name: modelData.accessibleName
|
||||
checked: root.authorityMode === index
|
||||
text: modelData.text
|
||||
onClicked: {
|
||||
root.authorityMode = index
|
||||
root.prepared = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormTextField {
|
||||
id: externalAuthorityField
|
||||
|
||||
Layout.preferredHeight: root.authorityMode === 2 ? 42 : 0
|
||||
visible: root.authorityMode === 2
|
||||
Accessible.name: qsTr("External mint authority account ID")
|
||||
placeholderText: qsTr("External authority account ID")
|
||||
onTextChanged: root.prepared = false
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: "#303030"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 3
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 15
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Metadata")
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: root.requiresMetadata ? qsTr("Required for non-fungible definitions") : qsTr("Optional for fungible definitions")
|
||||
}
|
||||
}
|
||||
|
||||
LogosSwitch {
|
||||
id: metadataSwitch
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Accessible.name: qsTr("Include metadata account")
|
||||
checked: root.hasMetadata
|
||||
enabled: !root.requiresMetadata
|
||||
onToggled: {
|
||||
root.metadataEnabled = checked;
|
||||
root.prepared = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("The program stores only standard, URI, creators, definition link, and a primary-sale date initialized to 0. It does not validate URI content or metadata schema.")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: root.hasMetadata
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
spacing: 7
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Metadata standard")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.metadataStandards
|
||||
|
||||
delegate: LogosRadioButton {
|
||||
required property int index
|
||||
required property var modelData
|
||||
|
||||
Accessible.name: modelData.accessibleName
|
||||
checked: root.metadataStandard === index
|
||||
text: modelData.text
|
||||
onClicked: {
|
||||
root.metadataStandard = index
|
||||
root.prepared = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("URI")
|
||||
}
|
||||
|
||||
FormTextField {
|
||||
id: metadataUriField
|
||||
|
||||
Accessible.name: qsTr("Metadata URI")
|
||||
placeholderText: qsTr("data:application/json;base64,... or external URI")
|
||||
onTextChanged: root.prepared = false
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Creators")
|
||||
}
|
||||
|
||||
FormTextField {
|
||||
id: creatorsField
|
||||
|
||||
Accessible.name: qsTr("Metadata creators string")
|
||||
placeholderText: qsTr("Creator account or attribution string")
|
||||
onTextChanged: root.prepared = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: "#303030"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 7
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 15
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("New target accounts")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("Every target must be a new default-valued account and authorize this transaction. The prototype can validate shape, not on-chain account state.")
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Definition target account")
|
||||
}
|
||||
|
||||
FormTextField {
|
||||
id: definitionTargetField
|
||||
|
||||
Accessible.name: qsTr("Definition target account ID")
|
||||
placeholderText: qsTr("New authorized account ID")
|
||||
onTextChanged: root.prepared = false
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: root.isFungible ? qsTr("Initial fungible holding target") : qsTr("NFT master holding target")
|
||||
}
|
||||
|
||||
FormTextField {
|
||||
id: holdingTargetField
|
||||
|
||||
Accessible.name: root.isFungible ? qsTr("Initial fungible holding target account ID") : qsTr("NFT master holding target account ID")
|
||||
placeholderText: qsTr("New authorized account ID")
|
||||
onTextChanged: root.prepared = false
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: root.hasMetadata
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
spacing: 7
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Metadata target account")
|
||||
}
|
||||
|
||||
FormTextField {
|
||||
id: metadataTargetField
|
||||
|
||||
Accessible.name: qsTr("Metadata target account ID")
|
||||
placeholderText: qsTr("New authorized account ID")
|
||||
onTextChanged: root.prepared = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: root.examplePatterns
|
||||
|
||||
delegate: CompactButton {
|
||||
required property var modelData
|
||||
|
||||
Accessible.name: modelData.accessibleName
|
||||
text: modelData.text
|
||||
onClicked: root.setPattern(modelData.pattern)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: previewPanel
|
||||
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: workbench.columnCount === 3 ? 410 : 0
|
||||
Layout.columnSpan: workbench.columnCount === 1 ? 1 : 1
|
||||
implicitHeight: previewContent.implicitHeight + 32
|
||||
radius: 16
|
||||
color: "#1B1B1B"
|
||||
border.color: "#303030"
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: previewContent
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 13
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Definition preview")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: previewHeadline.implicitHeight + 22
|
||||
radius: 10
|
||||
color: root.isFungible ? "#211914" : "#181D25"
|
||||
border.color: root.isFungible ? "#49301F" : "#31435D"
|
||||
border.width: 1
|
||||
|
||||
RowLayout {
|
||||
id: previewHeadline
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 11
|
||||
spacing: 10
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredHeight: 30
|
||||
Layout.preferredWidth: kindBadge.implicitWidth + 16
|
||||
radius: 15
|
||||
color: root.isFungible ? "#2D211A" : "#182534"
|
||||
border.color: root.isFungible ? "#6A4329" : "#40607A"
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
id: kindBadge
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: root.isFungible ? "#F2D8C7" : "#BFD8F4"
|
||||
font.pixelSize: 12
|
||||
font.weight: Font.DemiBold
|
||||
text: root.isFungible ? qsTr("Fungible") : qsTr("NFT collection")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#E7E1D8"
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 16
|
||||
font.weight: Font.DemiBold
|
||||
text: nameField.text.length > 0 ? nameField.text : qsTr("Untitled definition")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 9
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: root.isFungible ? qsTr("Stored total supply") : qsTr("Stored printable supply")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
color: root.validSupply ? "#E7E1D8" : "#F08A76"
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
text: root.supplyValue
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: "#303030"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Creation instruction")
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#F2D8C7"
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
text: root.instructionName
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: "#303030"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: root.isFungible
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Mint authority")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 14
|
||||
wrapMode: Text.Wrap
|
||||
text: root.authorityValue
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: !root.isFungible
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Master holding behavior")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 14
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("Starts with print balance %1. The master remains reserved, so at most %2 printed copies are possible.").arg(root.supplyValue).arg(root.validSupply && supplyField.text !== "0" ? qsTr("one fewer than the printable supply") : qsTr("none until a positive printable supply is set"))
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.hasMetadata ? 1 : 0
|
||||
visible: root.hasMetadata
|
||||
color: "#303030"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: root.hasMetadata
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: qsTr("Metadata account")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#E7E1D8"
|
||||
elide: Text.ElideMiddle
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
text: metadataTargetField.text.length > 0 ? metadataTargetField.text : qsTr("New metadata target required")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("%1 · URI and creators are stored as supplied.").arg(root.metadataStandard === 0 ? qsTr("Simple") : qsTr("Expanded"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: holdingOutcome.implicitHeight + 24
|
||||
radius: 10
|
||||
color: "#101010"
|
||||
border.color: "#343434"
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: holdingOutcome
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: root.isFungible ? qsTr("Initial fungible holding") : qsTr("Initial NFT master holding")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#E7E1D8"
|
||||
elide: Text.ElideMiddle
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
text: holdingTargetField.text.length > 0 ? holdingTargetField.text : qsTr("New holding target required")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 14
|
||||
wrapMode: Text.Wrap
|
||||
text: root.isFungible ? qsTr("Receives the full initial raw supply: %1.").arg(root.supplyValue) : qsTr("Receives the master state and print balance: %1.").arg(root.supplyValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 46
|
||||
Accessible.name: qsTr("Prepare token definition draft")
|
||||
enabled: root.canPrepare
|
||||
text: root.canPrepare ? qsTr("Prepare definition") : qsTr("Complete required targets")
|
||||
onClicked: root.prepareDefinition()
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
visible: root.prepared
|
||||
Layout.preferredHeight: visible ? implicitHeight : 0
|
||||
color: "#78C88D"
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
text: root.preparedMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: readinessPanel
|
||||
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: workbench.columnCount === 3 ? 300 : 0
|
||||
Layout.columnSpan: workbench.columnCount === 1 ? 1 : workbench.columnCount === 2 ? 2 : 1
|
||||
implicitHeight: readinessContent.implicitHeight + 32
|
||||
radius: 16
|
||||
color: "#181818"
|
||||
border.color: "#303030"
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: readinessContent
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.DemiBold
|
||||
text: qsTr("Protocol readiness")
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 13
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("Creation requires fresh authorized target accounts. Green checks confirm form shape only; account state remains unverified until a real client reads the chain.")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: "#303030"
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.readinessChecks
|
||||
|
||||
delegate: RowLayout {
|
||||
id: readinessRow
|
||||
|
||||
required property var modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 9
|
||||
|
||||
LogosBadge {
|
||||
color: readinessRow.modelData.ok ? Theme.palette.success : Theme.palette.error
|
||||
text: readinessRow.modelData.ok ? qsTr("Ready") : qsTr("Needs input")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
color: "#E7E1D8"
|
||||
font.pixelSize: 14
|
||||
font.weight: Font.Medium
|
||||
text: readinessRow.modelData.label
|
||||
}
|
||||
|
||||
Text {
|
||||
color: "#A9A098"
|
||||
font.pixelSize: 12
|
||||
text: readinessRow.modelData.detail
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: unsupportedNotice.implicitHeight + 22
|
||||
radius: 8
|
||||
color: "#211914"
|
||||
border.color: "#49301F"
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
id: unsupportedNotice
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 11
|
||||
color: "#F2D8C7"
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
text: root.hasMetadata ? qsTr("Metadata-backed definitions require typed serialization today; the generic IDL route cannot serialize the structured creation arguments.") : qsTr("This simple fungible path maps to the generic creation instruction, but this prototype never submits it.")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
color: "#8E8780"
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
text: qsTr("No on-chain symbol, decimal field, image, royalty, collection, or mutable metadata setting exists in the current token definition schema.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,368 @@
|
||||
import QtQuick 2.15
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
readonly property var fixtureDefinitions: [
|
||||
{
|
||||
"id": "9JDLE5Qr8dXKBstucN5sZi5tCCYy7SnfCEKax77JZTd7",
|
||||
"name": "Pebble",
|
||||
"type": "fungible",
|
||||
"definitionId": "9JDLE5Qr8dXKBstucN5sZi5tCCYy7SnfCEKax77JZTd7",
|
||||
"definitionHex": "7b464ff9dd0d3bc07f7e2e0b0667ccd066d85ad12be4c79fc55687a863910aa6",
|
||||
"holdingId": "DhKocL4KzaRbL25Dw3V8rDvTa6aNefxyCAb8F22Tyazn",
|
||||
"rawSupply": "7654321",
|
||||
"displaySupply": "7,654,321",
|
||||
"inferredDecimals": 0,
|
||||
"authorityMode": "fixed",
|
||||
"source": "testnet",
|
||||
"instruction": "new_fungible_definition",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "DhKocL4KzaRbL25Dw3V8rDvTa6aNefxyCAb8F22Tyazn",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "6419754",
|
||||
"displayBalance": "6,419,754"
|
||||
},
|
||||
{
|
||||
"id": "78Ynj85rpDFfAH2Tk1F296k6uJrU2h8mFoAsJg8sM3DZ",
|
||||
"wallet": "single-asset",
|
||||
"role": "recipient",
|
||||
"rawBalance": "1234567",
|
||||
"displayBalance": "1,234,567"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5u7LShcFeBwFubWbD1N6jBYMGonEuj1sfi2A6j9D5UK3",
|
||||
"name": "Aurora",
|
||||
"type": "fungible",
|
||||
"definitionId": "5u7LShcFeBwFubWbD1N6jBYMGonEuj1sfi2A6j9D5UK3",
|
||||
"definitionHex": "48c81cf032e601ca367fc9816b957dbf5c0e4c11cf7008e8f4581ec1a67aab42",
|
||||
"holdingId": "5rZuJSHTm2NggSBFbKuyZd6D3f7qWAhTB7YsgFZDYe8",
|
||||
"rawSupply": "98765432100",
|
||||
"displaySupply": "98,765.4321",
|
||||
"inferredDecimals": 6,
|
||||
"authorityMode": "fixed",
|
||||
"source": "testnet",
|
||||
"instruction": "new_fungible_definition",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "5rZuJSHTm2NggSBFbKuyZd6D3f7qWAhTB7YsgFZDYe8",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "90000000000",
|
||||
"displayBalance": "90,000"
|
||||
},
|
||||
{
|
||||
"id": "HrC5H28wnK64dsx4xDkUUPipxDvcNFmB9FizJ4vDEHLR",
|
||||
"wallet": "mixed-assets",
|
||||
"role": "recipient",
|
||||
"rawBalance": "8765432100",
|
||||
"displayBalance": "8,765.4321"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2TN8jTUxgxRZgATDGANqiNuALpAyUM3Lks6wiNYvSdDi",
|
||||
"name": "Cobalt",
|
||||
"type": "fungible",
|
||||
"definitionId": "2TN8jTUxgxRZgATDGANqiNuALpAyUM3Lks6wiNYvSdDi",
|
||||
"definitionHex": "159caef810ea545951b3bd913efe625ee45008c80865c330e72a72ed48b61649",
|
||||
"holdingId": "7mKCcw4dtQW6LyxFazZpB7XUisfGLRNfxBcWEiJaBvgZ",
|
||||
"rawSupply": "9876543210000",
|
||||
"displaySupply": "9,876,543.21",
|
||||
"inferredDecimals": 6,
|
||||
"authorityMode": "fixed",
|
||||
"source": "testnet",
|
||||
"instruction": "new_fungible_definition",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "7mKCcw4dtQW6LyxFazZpB7XUisfGLRNfxBcWEiJaBvgZ",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "9876543210000",
|
||||
"displayBalance": "9,876,543.21"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8wRnGribscuUhJJFca4nXwR8iAYtrpUU1JXLqZNXMSou",
|
||||
"name": "Meridian",
|
||||
"type": "fungible",
|
||||
"definitionId": "8wRnGribscuUhJJFca4nXwR8iAYtrpUU1JXLqZNXMSou",
|
||||
"definitionHex": "75f33110b185717209e3955f228d4a4448801d0ce8ba438a4a268050eeff3f44",
|
||||
"holdingId": "DNckKy9rUwohZS51ZYGxpw6Ke2WYusaBwqDecMzqCiqF",
|
||||
"rawSupply": "123456789012345678",
|
||||
"displaySupply": "123,456,789.012345678",
|
||||
"inferredDecimals": 9,
|
||||
"authorityMode": "fixed",
|
||||
"source": "testnet",
|
||||
"instruction": "new_fungible_definition",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "DNckKy9rUwohZS51ZYGxpw6Ke2WYusaBwqDecMzqCiqF",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "100000000000000000",
|
||||
"displayBalance": "100,000,000"
|
||||
},
|
||||
{
|
||||
"id": "4TkHNLiC6WQeFkeTmh6bAcLzYYhqbeJyRci92bXAcF8D",
|
||||
"wallet": "mixed-assets",
|
||||
"role": "recipient",
|
||||
"rawBalance": "23456789012345678",
|
||||
"displayBalance": "23,456,789.012345678"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "HwzCapv2fVYKhrdK68Q7QwdhfNkJDXrCGxqkW8e9UjDG",
|
||||
"name": "Quartz",
|
||||
"type": "fungible",
|
||||
"definitionId": "HwzCapv2fVYKhrdK68Q7QwdhfNkJDXrCGxqkW8e9UjDG",
|
||||
"definitionHex": "fbd107ca4bb66bc58f59ac2d32a759be3ee0fb453f8fecd1991c11837d9660c7",
|
||||
"holdingId": "AtBFdo6vXRoDHwadHPHvMk84bW3fLtjy7YHF5UeS7Trx",
|
||||
"rawSupply": "12345678901234567890123456",
|
||||
"displaySupply": "12,345,678.901234567890123456",
|
||||
"inferredDecimals": 18,
|
||||
"authorityMode": "fixed",
|
||||
"source": "testnet",
|
||||
"instruction": "new_fungible_definition",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "AtBFdo6vXRoDHwadHPHvMk84bW3fLtjy7YHF5UeS7Trx",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "10000000000000000000000000",
|
||||
"displayBalance": "10,000,000"
|
||||
},
|
||||
{
|
||||
"id": "3JqMVkxAnSVuvetAppW5g3jKrFKv5otyhHNUkYw859p2",
|
||||
"wallet": "mixed-assets",
|
||||
"role": "recipient",
|
||||
"rawBalance": "2345678901234567890123456",
|
||||
"displayBalance": "2,345,678.901234567890123456"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6juKZuiBbxwymyet38MnsvidbsomcQEYLR8jhaFYp7f4",
|
||||
"name": "Devium",
|
||||
"type": "fungible",
|
||||
"definitionId": "6juKZuiBbxwymyet38MnsvidbsomcQEYLR8jhaFYp7f4",
|
||||
"definitionHex": "5547fcb72644d95a385d313b887a96be41ff263bce6150b49fd87276839822bf",
|
||||
"holdingId": "63JPusYpdEEtri5ZHcLqfeM9XdnNsbc3pVdwhFnZB6ep",
|
||||
"rawSupply": "1000000750000000000000000000000",
|
||||
"displaySupply": "1,000,000,750,000",
|
||||
"inferredDecimals": 18,
|
||||
"authorityMode": "external",
|
||||
"authority": "HLf2CQotnxpjsrG98xrtUb7qoQcXufU6ARtMAdcuP55c",
|
||||
"authorityLabel": "Devium Authority",
|
||||
"source": "testnet",
|
||||
"instruction": "new_fungible_definition",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "63JPusYpdEEtri5ZHcLqfeM9XdnNsbc3pVdwhFnZB6ep",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "1000000750000000000000000000000",
|
||||
"displayBalance": "1,000,000,750,000"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Hqvym6HLGdu3WXZendqNKLL2feRmwFJ46CrVYhVBw1ti",
|
||||
"name": "Mint Condition",
|
||||
"type": "fungible",
|
||||
"definitionId": "Hqvym6HLGdu3WXZendqNKLL2feRmwFJ46CrVYhVBw1ti",
|
||||
"definitionHex": "fa43e74a97d79c5f907ff3edabda5ad89bfbd3b0922572e675d4ad3c7b6029c7",
|
||||
"holdingId": "HPYP64fjV6fRRk8rDMVFU4JXnh8g9SMpfFLMFVUBirVx",
|
||||
"rawSupply": "164803398874989484820",
|
||||
"displaySupply": "164,803,398,874.98948482",
|
||||
"inferredDecimals": 9,
|
||||
"authorityMode": "renounced",
|
||||
"initialAuthority": "Hqvym6HLGdu3WXZendqNKLL2feRmwFJ46CrVYhVBw1ti",
|
||||
"source": "testnet",
|
||||
"instruction": "new_fungible_definition",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "HPYP64fjV6fRRk8rDMVFU4JXnh8g9SMpfFLMFVUBirVx",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "164803398874989484820",
|
||||
"displayBalance": "164,803,398,874.98948482"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6L9bqMV8YiRpTNfqrZaZAyAFwqyNKHG2iQehRqANtVxk",
|
||||
"name": "Oops! All Metadata",
|
||||
"type": "fungible",
|
||||
"definitionId": "6L9bqMV8YiRpTNfqrZaZAyAFwqyNKHG2iQehRqANtVxk",
|
||||
"definitionHex": "4f3231d8a01e1d79f163bc27fce0c860a4a2f6890280e9d135eafbde0d68ed79",
|
||||
"holdingId": "B8juLRsBJLSAaFtFhwx3K3PpcxZ4aKkxnkh6LeFzdjBy",
|
||||
"metadataId": "AHUVjXRgedSGexq9ebwHZ2yfoXed2BKfMm2MQAcxE8WZ",
|
||||
"rawSupply": "424218967453",
|
||||
"displaySupply": "424,218.967453",
|
||||
"inferredDecimals": 6,
|
||||
"authorityMode": "fixed",
|
||||
"metadataStandard": "Simple",
|
||||
"metadataUri": "data:application/json;base64,eyJuYW1lIjoiT29wcyEgQWxsIE1ldGFkYXRhIiwiZGVzY3JpcHRpb24iOiJBIHRva2VuIHdpdGggbW9yZSBtZXRhZGF0YSB0aGFuIHNlbnNlLiIsIm1vZGUiOiJmaXhlZCIsInN0YW5kYXJkIjoiU2ltcGxlIn0=",
|
||||
"creators": "Department of Redundant Metadata",
|
||||
"description": "A token with more metadata than sense.",
|
||||
"source": "testnet",
|
||||
"instruction": "new_definition_with_metadata",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "B8juLRsBJLSAaFtFhwx3K3PpcxZ4aKkxnkh6LeFzdjBy",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "424118967453",
|
||||
"displayBalance": "424,118.967453"
|
||||
},
|
||||
{
|
||||
"id": "Dxd4WrxvZHmbCCVkFB7dzh6UYcKTc6GLegPS1XeAqowr",
|
||||
"wallet": "all-assets",
|
||||
"role": "recipient",
|
||||
"rawBalance": "100000000",
|
||||
"displayBalance": "100"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Hqfz9dfGeMgWbL9rhUnMPLgdQZUMczjNjUWCTRozkkk7",
|
||||
"name": "Pocket Lint Deluxe",
|
||||
"type": "fungible",
|
||||
"definitionId": "Hqfz9dfGeMgWbL9rhUnMPLgdQZUMczjNjUWCTRozkkk7",
|
||||
"definitionHex": "fa32f354408857006f8ea396b0419823bd04436eadb2d273d2618a46b4793ed8",
|
||||
"holdingId": "7aDup3sUWkFF2RZnCXyo78BEvaKT3Wf4dWrPTqaMeRXj",
|
||||
"metadataId": "GciAureLmwkCKuELgo2YzF1dYhh8Jcu7cMciquuY7no5",
|
||||
"rawSupply": "10000000000000000000000000",
|
||||
"displaySupply": "10,000,000",
|
||||
"inferredDecimals": 18,
|
||||
"authorityMode": "external",
|
||||
"authority": "2VavnvNLSTNUWhaG4Tkdw86WrRJx5dyoyQYij6KQzz3Y",
|
||||
"metadataStandard": "Expanded",
|
||||
"metadataUri": "data:application/json;base64,eyJuYW1lIjoiUG9ja2V0IExpbnQgRGVsdXhlIiwiZGVzY3JpcHRpb24iOiJQcmVtaXVtIGxpbnQsIG5vdyB0b2tlbml6ZWQuIiwibW9kZSI6ImV4dGVybmFsLWF1dGhvcml0eSIsImF1dGhvcml0eSI6IjJWYXZudk5MU1ROVVdoYUc0VGtkdzg2V3JSSng1ZHlveVFZaWo2S1F6ejNZIn0=",
|
||||
"creators": "2VavnvNLSTNUWhaG4Tkdw86WrRJx5dyoyQYij6KQzz3Y",
|
||||
"description": "Premium lint, now tokenized.",
|
||||
"source": "testnet",
|
||||
"instruction": "new_definition_with_metadata",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "7aDup3sUWkFF2RZnCXyo78BEvaKT3Wf4dWrPTqaMeRXj",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "10000000000000000000000000",
|
||||
"displayBalance": "10,000,000"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "14tAtixMByFyJrcZVyWibitnijLgd59PfyrjdnYzo8La",
|
||||
"name": "Sir Mints-a-Lot",
|
||||
"type": "fungible",
|
||||
"definitionId": "14tAtixMByFyJrcZVyWibitnijLgd59PfyrjdnYzo8La",
|
||||
"definitionHex": "00fe99e4fbd4c71f92e47c384c6235244c8cce39b6d6367e1e338eca0ffe01cb",
|
||||
"holdingId": "B1yzSPqaetRJx19aXUd7xpQje5iYNF2Qwr1SKbFLCf8F",
|
||||
"metadataId": "21ByKA4ZCYWm8pfPyhR1Q1tqYT2RRA8JGsDBX1cp24c7",
|
||||
"rawSupply": "1000000000000000000",
|
||||
"displaySupply": "1,000,000,000",
|
||||
"inferredDecimals": 9,
|
||||
"authorityMode": "self",
|
||||
"authority": "14tAtixMByFyJrcZVyWibitnijLgd59PfyrjdnYzo8La",
|
||||
"metadataStandard": "Simple",
|
||||
"metadataUri": "data:application/json;base64,eyJuYW1lIjoiU2lyIE1pbnRzLWEtTG90IiwiZGVzY3JpcHRpb24iOiJBIHNlbGYtYXV0aG9yaXplZCBtaW50aW5nIGVudGh1c2lhc3QuIiwibW9kZSI6InNlbGYtYXV0aG9yaXR5IiwiYXV0aG9yaXR5IjoiMTR0QXRpeE1CeUZ5SnJjWlZ5V2liaXRuaWpMZ2Q1OVBmeXJqZG5Zem84TGEifQ==",
|
||||
"creators": "14tAtixMByFyJrcZVyWibitnijLgd59PfyrjdnYzo8La",
|
||||
"description": "A self-authorized minting enthusiast.",
|
||||
"source": "testnet",
|
||||
"instruction": "new_definition_with_metadata",
|
||||
"holdings": [
|
||||
{
|
||||
"id": "B1yzSPqaetRJx19aXUd7xpQje5iYNF2Qwr1SKbFLCf8F",
|
||||
"wallet": "all-assets",
|
||||
"role": "treasury",
|
||||
"rawBalance": "1000000000000000000",
|
||||
"displayBalance": "1,000,000,000"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3sG2zN3fAXBvs9mdgFHFiJFSFiwiYyaR5HUA3gZZTSXt",
|
||||
"name": "Glitchlings",
|
||||
"type": "nonFungible",
|
||||
"definitionId": "3sG2zN3fAXBvs9mdgFHFiJFSFiwiYyaR5HUA3gZZTSXt",
|
||||
"definitionHex": "2a9769e40f12e6d1567bea757974fd9cbb504be1bd8e5a262e6ee5bfaf533d53",
|
||||
"holdingId": "5QTBFQpYDZDrj569WPBamVqdauNkRRo4DrYE4ZZZn2Pv",
|
||||
"metadataId": "BxAbq1EK6Hg6y8g296719V9HQJSet4yio5VG9Ub3YHtU",
|
||||
"authorityMode": "masterHolding",
|
||||
"metadataStandard": "Expanded",
|
||||
"metadataUri": "data:application/json;base64,eyJuYW1lIjoiR2xpdGNobGluZ3MiLCJkZXNjcmlwdGlvbiI6IkxFWiB0ZXN0bmV0IG5vbi1mdW5naWJsZSBjb2xsZWN0aW9uIiwibmV0d29yayI6InRlc3RuZXQiLCJhdXRob3JpdHkiOiI1UVRCRlFwWURaRHJqNTY5V1BCYW1WcWRhdU5rUlJvNERyWUU0WlpabjJQdiJ9",
|
||||
"creators": "5QTBFQpYDZDrj569WPBamVqdauNkRRo4DrYE4ZZZn2Pv",
|
||||
"description": "LEZ testnet non-fungible collection",
|
||||
"source": "testnet",
|
||||
"instruction": "new_definition_with_metadata",
|
||||
"printableCopies": 64,
|
||||
"holdings": [
|
||||
{
|
||||
"id": "5QTBFQpYDZDrj569WPBamVqdauNkRRo4DrYE4ZZZn2Pv",
|
||||
"wallet": "all-assets",
|
||||
"role": "nftMaster",
|
||||
"printBalance": 63
|
||||
},
|
||||
{
|
||||
"id": "96b6C9RCd5WCa42RgaRMT7ePEJh5GXLLhbBxd15qwA1i",
|
||||
"role": "nftPrintedCopy",
|
||||
"owned": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
property var draftDefinitions: []
|
||||
readonly property var allDefinitions: root.fixtureDefinitions.concat(root.draftDefinitions)
|
||||
|
||||
function findDefinition(id) {
|
||||
var wantedId = String(id || "")
|
||||
|
||||
for (var index = 0; index < root.allDefinitions.length; ++index) {
|
||||
var definition = root.allDefinitions[index]
|
||||
if (definition.id === wantedId || definition.definitionId === wantedId)
|
||||
return definition
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function visibleDefinitions(query, typeFilter) {
|
||||
var search = String(query || "").trim().toLowerCase()
|
||||
var expectedType = String(typeFilter || "all")
|
||||
return root.allDefinitions.filter(function(definition) {
|
||||
if (expectedType !== "all" && definition.type !== expectedType)
|
||||
return false
|
||||
if (!search)
|
||||
return true
|
||||
var fields = [definition.name, definition.id, definition.definitionId, definition.type]
|
||||
return fields.some(function(field) {
|
||||
return String(field || "").toLowerCase().indexOf(search) !== -1
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function shortAddress(address) {
|
||||
var value = String(address || "")
|
||||
return value.length > 13
|
||||
? value.substring(0, 6) + "..." + value.substring(value.length - 4)
|
||||
: value
|
||||
}
|
||||
|
||||
function addDraft(draft) {
|
||||
if (!draft)
|
||||
return null
|
||||
|
||||
root.draftDefinitions = root.draftDefinitions.concat([draft])
|
||||
return draft
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "TokenUiBackend.h"
|
||||
|
||||
#include "LogosWalletProvider.h"
|
||||
#include "WalletController.h"
|
||||
#include "logos_api.h"
|
||||
|
||||
TokenUiBackend::TokenUiBackend(LogosAPI* logosAPI, QObject* parent)
|
||||
: TokenUiBackendSimpleSource(parent),
|
||||
m_logosAPI(logosAPI ? logosAPI : new LogosAPI("token_ui", this)),
|
||||
m_wallet(std::make_unique<LogosWalletProvider>(m_logosAPI)),
|
||||
m_walletController(std::make_unique<WalletController>(
|
||||
*m_wallet, QStringLiteral("TokenUI")))
|
||||
{
|
||||
connect(m_walletController.get(), &WalletController::stateChanged,
|
||||
this, &TokenUiBackend::syncWalletState);
|
||||
syncWalletState();
|
||||
m_walletController->start();
|
||||
}
|
||||
|
||||
TokenUiBackend::~TokenUiBackend() = default;
|
||||
|
||||
WalletAccountModel* TokenUiBackend::accountModel() const
|
||||
{
|
||||
return m_walletController->accountModel();
|
||||
}
|
||||
|
||||
QString TokenUiBackend::createAccountPublic()
|
||||
{
|
||||
return m_walletController->createAccount(true);
|
||||
}
|
||||
|
||||
QString TokenUiBackend::createAccountPrivate()
|
||||
{
|
||||
return m_walletController->createAccount(false);
|
||||
}
|
||||
|
||||
QString TokenUiBackend::createNewDefault(QString password)
|
||||
{
|
||||
const QString mnemonic = m_walletController->createDefaultWallet(password);
|
||||
syncWalletState();
|
||||
return mnemonic;
|
||||
}
|
||||
|
||||
bool TokenUiBackend::openExisting()
|
||||
{
|
||||
const bool opened = m_walletController->open();
|
||||
syncWalletState();
|
||||
return opened;
|
||||
}
|
||||
|
||||
void TokenUiBackend::disconnectWallet()
|
||||
{
|
||||
m_walletController->disconnect();
|
||||
syncWalletState();
|
||||
}
|
||||
|
||||
void TokenUiBackend::syncWalletState()
|
||||
{
|
||||
const WalletUiState& state = m_walletController->state();
|
||||
setIsWalletOpen(state.isWalletOpen);
|
||||
setWalletExists(state.walletExists);
|
||||
setWalletHome(state.walletHome);
|
||||
setSequencerAddr(state.sequencerAddress);
|
||||
setSequencerReachable(state.sequencerReachable);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef TOKEN_UI_BACKEND_H
|
||||
#define TOKEN_UI_BACKEND_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "rep_TokenUiBackend_source.h"
|
||||
|
||||
#include "WalletAccountModel.h"
|
||||
|
||||
class LogosAPI;
|
||||
class LogosWalletProvider;
|
||||
class WalletController;
|
||||
|
||||
// Token UI backend. Wallet lifecycle and account state are delegated to the
|
||||
// repository's shared WalletController; this class only adapts that state to
|
||||
// the TokenUiBackend QtRO contract.
|
||||
class TokenUiBackend : public TokenUiBackendSimpleSource {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(WalletAccountModel* accountModel READ accountModel CONSTANT)
|
||||
|
||||
public:
|
||||
explicit TokenUiBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr)
|
||||
;
|
||||
~TokenUiBackend() override;
|
||||
|
||||
WalletAccountModel* accountModel() const;
|
||||
|
||||
public slots:
|
||||
QString createAccountPublic() override;
|
||||
QString createAccountPrivate() override;
|
||||
QString createNewDefault(QString password) override;
|
||||
bool openExisting() override;
|
||||
void disconnectWallet() override;
|
||||
|
||||
private:
|
||||
void syncWalletState();
|
||||
|
||||
LogosAPI* m_logosAPI;
|
||||
std::unique_ptr<LogosWalletProvider> m_wallet;
|
||||
std::unique_ptr<WalletController> m_walletController;
|
||||
};
|
||||
|
||||
#endif // TOKEN_UI_BACKEND_H
|
||||
@@ -0,0 +1,29 @@
|
||||
// QtRO view contract for the Token UI backend. PROPs auto-sync to every QML
|
||||
// replica; SLOTs are the async surface QML calls via logos.watch(...).
|
||||
// The account list is exposed separately as a Q_PROPERTY model on the backend
|
||||
// (reached from QML via logos.model("token_ui", "accountModel")).
|
||||
class TokenUiBackend
|
||||
{
|
||||
PROP(bool isWalletOpen READONLY)
|
||||
PROP(bool walletExists READONLY)
|
||||
PROP(QString walletHome READONLY)
|
||||
PROP(QString sequencerAddr READONLY)
|
||||
// Whether the configured sequencer answered the last reachability probe.
|
||||
// Defaults true so the UI doesn't flash a warning before the first check.
|
||||
PROP(bool sequencerReachable READONLY)
|
||||
|
||||
// Account management
|
||||
SLOT(QString createAccountPublic())
|
||||
SLOT(QString createAccountPrivate())
|
||||
|
||||
// Creates a fresh per-app wallet at walletHome and returns its BIP39
|
||||
// mnemonic (empty on failure), which is the only chance to record it.
|
||||
SLOT(QString createNewDefault(QString password))
|
||||
|
||||
// Re-open the existing on-disk wallet after a disconnect.
|
||||
SLOT(bool openExisting())
|
||||
// Close this app's wallet view (lock); does not delete the wallet and, in
|
||||
// Basecamp, does not close the wallet other apps share.
|
||||
SLOT(void disconnectWallet())
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "TokenUiPlugin.h"
|
||||
#include "TokenUiBackend.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
TokenUiPlugin::TokenUiPlugin(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
TokenUiPlugin::~TokenUiPlugin() = default;
|
||||
|
||||
void TokenUiPlugin::initLogos(LogosAPI* api)
|
||||
{
|
||||
if (m_backend) return;
|
||||
m_backend = new TokenUiBackend(api, this);
|
||||
setBackend(m_backend);
|
||||
qDebug() << "TokenUiPlugin: backend initialized";
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef TOKEN_UI_PLUGIN_H
|
||||
#define TOKEN_UI_PLUGIN_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QtPlugin> // for Q_PLUGIN_METADATA, Q_INTERFACES
|
||||
#include "TokenUiPluginInterface.h"
|
||||
#include "LogosViewPluginBase.h"
|
||||
|
||||
class LogosAPI;
|
||||
class TokenUiBackend;
|
||||
|
||||
// Thin plugin entry point. Holds a TokenUiBackend and lets the generated
|
||||
// view-plugin base expose it to ui-host.
|
||||
class TokenUiPlugin : public QObject,
|
||||
public TokenUiPluginInterface,
|
||||
public TokenUiBackendViewPluginBase
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID TokenUiPluginInterface_iid FILE "../metadata.json")
|
||||
Q_INTERFACES(TokenUiPluginInterface)
|
||||
|
||||
public:
|
||||
explicit TokenUiPlugin(QObject* parent = nullptr);
|
||||
~TokenUiPlugin() override;
|
||||
|
||||
QString name() const override { return "token_ui"; }
|
||||
QString version() const override { return "0.1.0"; }
|
||||
|
||||
// Called by ui-host after plugin load. Creates the backend and wires it
|
||||
// up with the provided LogosAPI.
|
||||
Q_INVOKABLE void initLogos(LogosAPI* api);
|
||||
|
||||
private:
|
||||
TokenUiBackend* m_backend = nullptr;
|
||||
};
|
||||
|
||||
#endif // TOKEN_UI_PLUGIN_H
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef TOKEN_UI_PLUGIN_INTERFACE_H
|
||||
#define TOKEN_UI_PLUGIN_INTERFACE_H
|
||||
|
||||
#include <QtPlugin> // for Q_DECLARE_INTERFACE
|
||||
#include "interface.h"
|
||||
|
||||
// Marker interface used by Qt's plugin loader to identify the Token UI plugin.
|
||||
// The actual API surface (slots, properties, signals) lives in
|
||||
// TokenUiBackend.rep — this header only carries the IID.
|
||||
class TokenUiPluginInterface : public PluginInterface
|
||||
{
|
||||
public:
|
||||
virtual ~TokenUiPluginInterface() = default;
|
||||
};
|
||||
|
||||
#define TokenUiPluginInterface_iid "org.logos.TokenUiPluginInterface"
|
||||
Q_DECLARE_INTERFACE(TokenUiPluginInterface, TokenUiPluginInterface_iid)
|
||||
|
||||
#endif // TOKEN_UI_PLUGIN_INTERFACE_H
|
||||
Reference in New Issue
Block a user