Files
lez-programs/apps/token/qml/pages/CreatePage.qml
T
Ricardo Guilherme Schmidt 470e06c6c3 feat(token-ui): integrate Basecamp token module
Add portable Basecamp packaging and an inspector-driven end-to-end flow.
2026-08-20 13:42:46 +02:00

1816 lines
84 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls.Basic
import QtQuick.Layouts
Item {
id: root
objectName: "tokenCreatePage"
property var store: null
property var backend: null
property var runtime: null
property int tokenKind: 0
property int authorityMode: 0
property bool metadataEnabled: false
property int metadataStandard: 0
property int step: 0
property bool prepared: false
property string preparedMessage: ""
property string errorMessage: ""
property bool submitting: false
property bool accountBusy: false
signal requestInspect()
onVisibleChanged: {
if (visible) {
step = 0;
prepared = false;
preparedMessage = "";
errorMessage = "";
submitting = false;
scroll.contentY = 0;
}
}
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 ? "createNonFungible" : hasMetadata ? "createFungibleWithMetadata" : "createFungible"
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("Master holding controls printing") : authorityMode === 0 ? qsTr("Fixed supply") : authorityMode === 1 ? qsTr("Definition account") : externalAuthorityField.text.length > 0 ? externalAuthorityField.text : qsTr("External account 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 canContinue: validSupply && validExternalAuthority
readonly property bool canPrepare: canContinue && validDefinitionTarget && validHoldingTarget && validMetadataTarget
readonly property bool canSubmit: canPrepare && root.backend !== null && root.backend.isWalletOpen && !root.submitting
readonly property int validTargetCount: (validDefinitionTarget ? 1 : 0) + (validHoldingTarget ? 1 : 0) + (hasMetadata && validMetadataTarget ? 1 : 0)
readonly property int targetCount: hasMetadata ? 3 : 2
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 /^[0-9a-fA-F]{64}$/.test(value) || /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value);
}
function clearDraftState() {
prepared = false;
preparedMessage = "";
errorMessage = "";
}
function watch(result, success, failure) {
if (root.runtime && root.runtime.watch)
root.runtime.watch(result, success, failure);
else if (failure)
failure(qsTr("Runtime is not ready."));
}
function createTargetAccounts() {
if (!root.backend || !root.backend.isWalletOpen || root.accountBusy)
return;
var fields = [definitionTargetField, holdingTargetField];
if (root.hasMetadata)
fields.push(metadataTargetField);
var pendingFields = [];
for (var index = 0; index < fields.length; ++index) {
if (!fields[index].text)
pendingFields.push(fields[index]);
}
if (pendingFields.length === 0)
return;
root.accountBusy = true;
function createNext(fieldIndex) {
if (fieldIndex >= pendingFields.length) {
root.accountBusy = false;
root.clearDraftState();
return;
}
root.watch(root.backend.createAccountPublic(), function(accountId) {
if (!accountId) {
root.accountBusy = false;
root.errorMessage = qsTr("Could not create a public wallet account.");
return;
}
pendingFields[fieldIndex].text = String(accountId);
createNext(fieldIndex + 1);
}, function(error) {
root.accountBusy = false;
root.errorMessage = qsTr("Could not create a public wallet account: %1").arg(error);
});
}
createNext(0);
}
function selectTokenKind(kind) {
tokenKind = kind;
if (requiresMetadata)
metadataEnabled = true;
clearDraftState();
}
function setPattern(pattern) {
clearDraftState();
step = 0;
externalAuthorityField.text = "";
definitionTargetField.text = "";
holdingTargetField.text = "";
metadataTargetField.text = "";
metadataUriField.text = "";
creatorsField.text = "";
if (pattern === "fixed") {
tokenKind = 0;
authorityMode = 0;
metadataEnabled = false;
metadataStandard = 0;
nameField.text = qsTr("Fixed supply token");
supplyField.text = "7654321";
return;
}
if (pattern === "metadata") {
tokenKind = 0;
authorityMode = 2;
metadataEnabled = true;
metadataStandard = 1;
nameField.text = qsTr("Metadata token");
supplyField.text = "10000000000000000000000000";
metadataUriField.text = "data:application/json;base64,...";
creatorsField.text = qsTr("Creator or authority label");
return;
}
tokenKind = 1;
authorityMode = 0;
metadataEnabled = true;
metadataStandard = 1;
nameField.text = qsTr("NFT collection");
supplyField.text = "64";
metadataUriField.text = "data:application/json;base64,...";
creatorsField.text = qsTr("Master-holding creator");
}
function prepareDefinition() {
if (!canSubmit)
return;
var mintAuthority = !isFungible ? "" : authorityMode === 0 ? "none" : authorityMode === 1 ? "self" : externalAuthorityField.text;
var metadataStandardValue = metadataStandard === 0 ? "simple" : "expanded";
var pending;
root.submitting = true;
root.errorMessage = "";
root.preparedMessage = qsTr("Submitting to the Token Program…");
if (!isFungible && root.backend) {
pending = root.backend.createNonFungible(
definitionTargetField.text,
holdingTargetField.text,
metadataTargetField.text,
nameField.text,
supplyField.text,
metadataStandardValue,
metadataUriField.text,
creatorsField.text);
} else if (hasMetadata && root.backend) {
pending = root.backend.createFungibleWithMetadata(
definitionTargetField.text,
holdingTargetField.text,
metadataTargetField.text,
nameField.text,
supplyField.text,
mintAuthority,
metadataStandardValue,
metadataUriField.text,
creatorsField.text);
} else if (root.backend) {
pending = root.backend.createFungible(
definitionTargetField.text,
holdingTargetField.text,
nameField.text,
supplyField.text,
mintAuthority);
}
root.watch(pending, function(result) {
root.submitting = false;
if (!result || result.status !== "ok") {
root.prepared = false;
root.errorMessage = qsTr("Token Program rejected the request: %1").arg(result && result.error ? result.error : qsTr("unknown_error"));
return;
}
var transactionId = String(result.transactionId || "");
var definitionType = isFungible ? "fungible" : "nonFungible";
var draft = {
id: definitionTargetField.text,
name: nameField.text.length > 0 ? nameField.text : qsTr("Untitled definition"),
type: definitionType,
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 : "",
authorityLabel: isFungible && authorityMode === 2 ? qsTr("External authority account") : "",
metadataStandard: hasMetadata ? (metadataStandard === 0 ? "Simple" : "Expanded") : "",
metadataUri: hasMetadata ? metadataUriField.text : "",
creators: hasMetadata ? creatorsField.text : "",
description: qsTr("Submitted to the Token Program."),
source: "pending",
instruction: instructionName,
printableCopies: !isFungible ? supplyField.text : "",
masterHolding: !isFungible ? holdingTargetField.text : "",
transactionId: transactionId,
definition: {
id: definitionTargetField.text,
hex: definitionTargetField.text,
name: nameField.text,
type: definitionType,
totalSupplyRaw: isFungible ? supplyField.text : undefined,
printableSupply: !isFungible ? supplyField.text : undefined,
mintAuthority: isFungible && authorityMode === 2 ? externalAuthorityField.text : isFungible && authorityMode === 1 ? definitionTargetField.text : undefined,
metadataId: hasMetadata ? metadataTargetField.text : undefined
},
holding: {
id: holdingTargetField.text,
wallet: "connected wallet",
role: !isFungible ? "nftMaster" : "fungible",
rawBalance: isFungible ? supplyField.text : undefined,
printBalance: !isFungible ? supplyField.text : undefined
},
holdings: [{
id: holdingTargetField.text,
wallet: "connected wallet",
role: !isFungible ? "nftMaster" : "fungible",
rawBalance: isFungible ? supplyField.text : undefined,
printBalance: !isFungible ? supplyField.text : undefined
}]
};
if (hasMetadata) {
draft.metadata = {
id: metadataTargetField.text,
standard: metadataStandard === 0 ? "Simple" : "Expanded",
uri: metadataUriField.text,
creators: creatorsField.text
};
}
if (store && store.addDraft)
store.addDraft(draft);
root.prepared = true;
root.preparedMessage = transactionId.length > 0
? qsTr("Transaction submitted · %1").arg(transactionId)
: qsTr("Transaction submitted.");
}, function(error) {
root.submitting = false;
root.prepared = false;
root.errorMessage = qsTr("Token Program request failed: %1").arg(error);
});
}
Rectangle {
anchors.fill: parent
color: "#151515"
}
Menu {
id: examplesMenu
objectName: "tokenExamplesMenu"
MenuItem {
objectName: "tokenFixedTemplate"
text: qsTr("Fixed supply")
onTriggered: root.setPattern("fixed")
}
MenuItem {
objectName: "tokenMetadataTemplate"
text: qsTr("Metadata-backed fungible")
onTriggered: root.setPattern("metadata")
}
MenuItem {
objectName: "tokenNftTemplate"
text: qsTr("NFT collection")
onTriggered: root.setPattern("nft")
}
}
Flickable {
id: scroll
anchors.fill: parent
clip: true
contentWidth: width
contentHeight: contentColumn.implicitHeight + 32
flickableDirection: Flickable.VerticalFlick
ScrollBar.vertical: ScrollBar {
id: verticalScrollBar
parent: scroll.parent
anchors.top: scroll.top
anchors.right: scroll.right
anchors.bottom: scroll.bottom
anchors.topMargin: 4
anchors.rightMargin: 4
anchors.bottomMargin: 4
policy: scroll.contentHeight > scroll.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
active: true
visible: policy === ScrollBar.AlwaysOn
width: 12
z: 10
background: Rectangle {
radius: 6
color: "#292929"
}
contentItem: Rectangle {
implicitWidth: 8
implicitHeight: 32
radius: 4
color: verticalScrollBar.pressed ? "#F26A21" : "#9A8C81"
opacity: 1
}
}
ColumnLayout {
id: contentColumn
width: Math.max(240, Math.min(scroll.width - 32, 1120))
x: Math.max(16, (scroll.width - width) / 2)
y: 16
spacing: 12
ColumnLayout {
id: pageHeader
Layout.fillWidth: true
spacing: 8
ColumnLayout {
Layout.fillWidth: true
Layout.minimumWidth: 0
spacing: 3
Text {
Layout.fillWidth: true
Layout.minimumWidth: 0
color: "#E7E1D8"
font.pixelSize: 28
font.weight: Font.DemiBold
wrapMode: Text.Wrap
text: qsTr("Create definition")
}
Text {
Layout.fillWidth: true
Layout.minimumWidth: 0
color: "#A9A098"
font.pixelSize: 14
wrapMode: Text.Wrap
text: qsTr("Configure the token, confirm its account targets, then create the definition.")
}
}
RowLayout {
id: progressRow
Layout.fillWidth: true
Layout.minimumWidth: 0
spacing: 5
Repeater {
model: [qsTr("Configure"), qsTr("Account targets"), qsTr("Review")]
delegate: RowLayout {
id: stepItem
required property int index
required property var modelData
Layout.fillWidth: true
Layout.minimumWidth: 0
spacing: 5
Rectangle {
Layout.preferredWidth: 24
Layout.preferredHeight: 24
radius: 12
color: root.step === stepItem.index ? "#F26A21" : root.step > stepItem.index ? "#183222" : "#202020"
border.color: root.step === stepItem.index ? "#F26A21" : root.step > stepItem.index ? "#39C06A" : "#343434"
border.width: 1
Text {
anchors.centerIn: parent
color: root.step === stepItem.index ? "#151515" : root.step > stepItem.index ? "#78C88D" : "#A9A098"
font.pixelSize: 12
font.weight: Font.DemiBold
text: (stepItem.index + 1).toString()
}
}
Text {
Layout.fillWidth: true
Layout.minimumWidth: 0
color: root.step === stepItem.index ? "#E7E1D8" : "#A9A098"
font.pixelSize: 13
font.weight: root.step === stepItem.index ? Font.DemiBold : Font.Normal
horizontalAlignment: Text.AlignLeft
wrapMode: Text.Wrap
text: stepItem.modelData
verticalAlignment: Text.AlignVCenter
}
Rectangle {
Layout.fillWidth: true
Layout.minimumWidth: 0
Layout.preferredHeight: 1
visible: stepItem.index < 2
color: root.step > stepItem.index ? "#39C06A" : "#343434"
}
}
}
}
}
GridLayout {
id: stage
readonly property int columnCount: contentColumn.width >= 860 ? 2 : 1
Layout.fillWidth: true
Layout.minimumWidth: 0
columns: columnCount
columnSpacing: 12
rowSpacing: 12
Rectangle {
id: mainPanel
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.minimumWidth: 0
Layout.preferredWidth: stage.columnCount === 2 ? 640 : 0
Layout.maximumWidth: stage.columnCount === 2 ? 680 : 1120
implicitHeight: editor.implicitHeight + 32
radius: 16
color: "#1B1B1B"
border.color: "#303030"
border.width: 1
ColumnLayout {
id: editor
anchors.fill: parent
anchors.margins: 14
spacing: 10
ColumnLayout {
Layout.fillWidth: true
spacing: 3
Text {
color: "#E7E1D8"
font.pixelSize: 20
font.weight: Font.DemiBold
text: root.step === 0 ? qsTr("Token settings") : root.step === 1 ? qsTr("Account targets") : qsTr("Review definition")
}
Text {
Layout.fillWidth: true
color: "#A9A098"
font.pixelSize: 13
wrapMode: Text.Wrap
text: root.step === 0 ? qsTr("Choose the fields that define this token.") : root.step === 1 ? qsTr("These accounts are created with the token definition.") : qsTr("Review the definition before creating it.")
}
}
ColumnLayout {
id: configureView
Layout.fillWidth: true
Layout.preferredHeight: visible ? implicitHeight : 0
spacing: 10
visible: root.step === 0
Rectangle {
id: kindTabs
Layout.fillWidth: true
Layout.preferredHeight: 44
Layout.minimumHeight: 44
Layout.maximumHeight: 44
radius: 8
color: "#101010"
border.color: "#343434"
border.width: 1
RowLayout {
anchors.fill: parent
spacing: 0
Button {
id: fungibleTab
objectName: "tokenFungibleTab"
property bool selected: root.tokenKind === 0
Layout.fillWidth: true
Layout.fillHeight: true
text: qsTr("Fungible")
activeFocusOnTab: true
Accessible.name: qsTr("Create fungible token definition")
onClicked: root.selectTokenKind(0)
contentItem: Text {
color: fungibleTab.selected ? "#F2D8C7" : "#A9A098"
font.pixelSize: 14
font.weight: fungibleTab.selected ? Font.DemiBold : Font.Normal
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: fungibleTab.text
}
background: Rectangle {
radius: 7
color: fungibleTab.selected ? "#211914" : "transparent"
border.color: fungibleTab.activeFocus ? "#FFB26B" : fungibleTab.selected ? "#F26A21" : "transparent"
border.width: fungibleTab.activeFocus ? 2 : fungibleTab.selected ? 1 : 0
}
}
Button {
id: nonFungibleTab
objectName: "tokenNonFungibleTab"
property bool selected: root.tokenKind === 1
Layout.fillWidth: true
Layout.fillHeight: true
text: qsTr("Non-fungible")
activeFocusOnTab: true
Accessible.name: qsTr("Create non-fungible token definition")
onClicked: root.selectTokenKind(1)
contentItem: Text {
color: nonFungibleTab.selected ? "#F2D8C7" : "#A9A098"
font.pixelSize: 14
font.weight: nonFungibleTab.selected ? Font.DemiBold : Font.Normal
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: nonFungibleTab.text
}
background: Rectangle {
radius: 7
color: nonFungibleTab.selected ? "#211914" : "transparent"
border.color: nonFungibleTab.activeFocus ? "#FFB26B" : nonFungibleTab.selected ? "#F26A21" : "transparent"
border.width: nonFungibleTab.activeFocus ? 2 : nonFungibleTab.selected ? 1 : 0
}
}
}
}
Text {
Layout.fillWidth: true
color: "#A9A098"
font.pixelSize: 13
wrapMode: Text.Wrap
text: root.isFungible ? qsTr("One initial holding receives the full raw supply.") : qsTr("Metadata is required. The initial master holding controls printing.")
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: "#303030"
}
Text {
color: "#E7E1D8"
font.pixelSize: 16
font.weight: Font.DemiBold
text: qsTr("Identity")
}
Text {
color: "#A9A098"
font.pixelSize: 12
text: qsTr("Name")
}
TextField {
id: nameField
objectName: "tokenNameField"
Layout.fillWidth: true
Layout.preferredHeight: 44
Accessible.name: qsTr("Token definition name")
color: "#E7E1D8"
font.pixelSize: 15
placeholderText: qsTr("Name stored on the definition")
placeholderTextColor: "#8E8780"
selectByMouse: true
text: qsTr("New token")
onTextEdited: root.clearDraftState()
background: Rectangle {
radius: 7
color: "#101010"
border.color: nameField.activeFocus ? "#F26A21" : "#343434"
border.width: 1
}
}
Text {
color: "#A9A098"
font.pixelSize: 12
text: root.supplyLabel
}
TextField {
id: supplyField
objectName: "tokenSupplyField"
Layout.fillWidth: true
Layout.preferredHeight: 44
Accessible.name: root.supplyLabel
color: "#E7E1D8"
font.pixelSize: 15
inputMethodHints: Qt.ImhDigitsOnly
placeholderText: qsTr("Unsigned 128-bit integer")
placeholderTextColor: "#8E8780"
selectByMouse: true
text: "1000000"
validator: RegularExpressionValidator {
regularExpression: /^[0-9]*$/
}
onTextEdited: root.clearDraftState()
background: Rectangle {
radius: 7
color: "#101010"
border.color: supplyField.activeFocus ? "#F26A21" : supplyField.text.length > 0 && !root.validSupply ? "#D85F4B" : "#343434"
border.width: 1
}
}
Text {
Layout.fillWidth: true
color: root.validSupply ? "#A9A098" : "#F08A76"
font.pixelSize: 12
wrapMode: Text.Wrap
text: root.validSupply ? qsTr("Raw u128 value. Display decimals are inferred by clients, not stored here.") : qsTr("Enter an unsigned 128-bit integer between 0 and 340282366920938463463374607431768211455.")
}
ColumnLayout {
Layout.fillWidth: true
visible: root.isFungible
Layout.preferredHeight: visible ? implicitHeight : 0
spacing: 8
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: "#303030"
}
Text {
color: "#E7E1D8"
font.pixelSize: 16
font.weight: Font.DemiBold
text: qsTr("Mint authority")
}
ComboBox {
id: authoritySelector
objectName: "tokenAuthoritySelector"
Layout.fillWidth: true
Layout.preferredHeight: 44
Accessible.name: qsTr("Mint authority policy")
currentIndex: root.authorityMode
model: [qsTr("Fixed supply — no future minting"), qsTr("Self authority — definition account mints"), qsTr("External authority — another account mints")]
onActivated: {
root.authorityMode = currentIndex;
root.clearDraftState();
}
contentItem: Text {
leftPadding: 12
rightPadding: authoritySelector.indicator.width + authoritySelector.spacing
color: "#E7E1D8"
elide: Text.ElideRight
font.pixelSize: 14
text: authoritySelector.currentText
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 7
color: "#101010"
border.color: authoritySelector.activeFocus ? "#F26A21" : "#343434"
border.width: 1
}
}
TextField {
id: externalAuthorityField
Layout.fillWidth: true
Layout.preferredHeight: root.authorityMode === 2 ? 44 : 0
Accessible.name: qsTr("External mint authority account ID")
clip: true
color: "#E7E1D8"
font.pixelSize: 14
placeholderText: qsTr("External authority account ID")
placeholderTextColor: "#8E8780"
selectByMouse: true
visible: root.authorityMode === 2
onTextEdited: root.clearDraftState()
background: Rectangle {
radius: 7
color: "#101010"
border.color: externalAuthorityField.activeFocus ? "#F26A21" : externalAuthorityField.text.length > 0 && !root.validExternalAuthority ? "#D85F4B" : "#343434"
border.width: 1
}
}
}
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: 16
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")
}
}
CheckBox {
id: metadataCheckBox
objectName: "tokenMetadataCheckBox"
Layout.alignment: Qt.AlignVCenter
Accessible.name: qsTr("Include metadata account")
checked: root.hasMetadata
enabled: !root.requiresMetadata
text: qsTr("Include")
onToggled: {
root.metadataEnabled = checked;
root.clearDraftState();
}
contentItem: Text {
leftPadding: metadataCheckBox.indicator.width + metadataCheckBox.spacing
color: metadataCheckBox.enabled ? "#B8ADA3" : "#6D6761"
font.pixelSize: 12
text: metadataCheckBox.text
verticalAlignment: Text.AlignVCenter
}
indicator: Rectangle {
implicitWidth: 18
implicitHeight: 18
x: metadataCheckBox.leftPadding
y: metadataCheckBox.topPadding + (metadataCheckBox.availableHeight - height) / 2
radius: 4
color: metadataCheckBox.checked ? "#F26A21" : "#101010"
border.color: metadataCheckBox.activeFocus ? "#FFB26B" : metadataCheckBox.checked ? "#F26A21" : "#8E8780"
border.width: metadataCheckBox.activeFocus ? 2 : 1
Rectangle {
anchors.centerIn: parent
width: 6
height: 6
radius: 3
color: "#151515"
visible: metadataCheckBox.checked
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
visible: root.hasMetadata
Layout.preferredHeight: visible ? implicitHeight : 0
spacing: 8
Text {
Layout.fillWidth: true
color: "#A9A098"
font.pixelSize: 12
wrapMode: Text.Wrap
text: qsTr("Only the standard, URI, creators, definition link, and primary-sale date are stored. URI content is not validated by the program.")
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Text {
color: "#A9A098"
font.pixelSize: 12
text: qsTr("Standard")
}
ComboBox {
id: metadataStandardSelector
Layout.preferredWidth: 150
Layout.preferredHeight: 36
Accessible.name: qsTr("Metadata standard")
currentIndex: root.metadataStandard
model: [qsTr("Simple"), qsTr("Expanded")]
onActivated: {
root.metadataStandard = currentIndex;
root.clearDraftState();
}
background: Rectangle {
radius: 7
color: "#101010"
border.color: metadataStandardSelector.activeFocus ? "#F26A21" : "#343434"
border.width: 1
}
}
Item {
Layout.fillWidth: true
}
}
Text {
color: "#A9A098"
font.pixelSize: 12
text: qsTr("URI")
}
TextField {
id: metadataUriField
Layout.fillWidth: true
Layout.preferredHeight: 44
Accessible.name: qsTr("Metadata URI")
color: "#E7E1D8"
font.pixelSize: 14
placeholderText: qsTr("data:application/json;base64,... or external URI")
placeholderTextColor: "#8E8780"
selectByMouse: true
onTextEdited: root.clearDraftState()
background: Rectangle {
radius: 7
color: "#101010"
border.color: metadataUriField.activeFocus ? "#F26A21" : "#343434"
border.width: 1
}
}
Text {
color: "#A9A098"
font.pixelSize: 12
text: qsTr("Creators")
}
TextField {
id: creatorsField
Layout.fillWidth: true
Layout.preferredHeight: 44
Accessible.name: qsTr("Metadata creators string")
color: "#E7E1D8"
font.pixelSize: 14
placeholderText: qsTr("Creator account or attribution string")
placeholderTextColor: "#8E8780"
selectByMouse: true
onTextEdited: root.clearDraftState()
background: Rectangle {
radius: 7
color: "#101010"
border.color: creatorsField.activeFocus ? "#F26A21" : "#343434"
border.width: 1
}
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Button {
id: examplesButton
objectName: "tokenExamplesButton"
Layout.preferredHeight: 36
text: qsTr("Use template")
activeFocusOnTab: true
Accessible.name: qsTr("Choose a token definition template")
onClicked: examplesMenu.popup(examplesButton, Qt.point(0, examplesButton.height))
contentItem: Text {
color: "#B8ADA3"
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: examplesButton.text
}
background: Rectangle {
radius: 7
color: "#101010"
border.color: examplesButton.activeFocus ? "#FFB26B" : "#343434"
border.width: examplesButton.activeFocus ? 2 : 1
}
}
Item {
Layout.fillWidth: true
}
}
Button {
id: continueButton
objectName: "tokenContinueButton"
Layout.fillWidth: true
Layout.preferredHeight: visible ? 46 : 0
activeFocusOnTab: true
Accessible.name: qsTr("Continue to account targets")
enabled: root.canContinue
text: root.canContinue ? qsTr("Continue to account targets") : qsTr("Complete required fields")
visible: stage.columnCount === 1
onClicked: root.step = 1
contentItem: Text {
color: continueButton.enabled ? "#FFFFFF" : "#8E8780"
font.pixelSize: 15
font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: continueButton.text
}
background: Rectangle {
radius: 8
color: continueButton.enabled ? "#F26A21" : "#282522"
border.color: continueButton.activeFocus ? "#FFB26B" : continueButton.enabled ? "#F26A21" : "#3C3833"
border.width: continueButton.activeFocus ? 2 : 1
}
}
}
ColumnLayout {
id: accountsView
Layout.fillWidth: true
Layout.preferredHeight: visible ? implicitHeight : 0
spacing: 12
visible: root.step === 1
Rectangle {
Layout.fillWidth: true
implicitHeight: accountList.implicitHeight + 24
radius: 10
color: "#101010"
border.color: "#343434"
border.width: 1
ColumnLayout {
id: accountList
anchors.fill: parent
anchors.margins: 12
spacing: 6
Text {
color: "#E7E1D8"
font.pixelSize: 15
font.weight: Font.DemiBold
text: qsTr("What this definition creates")
}
Text {
Layout.fillWidth: true
color: "#A9A098"
font.pixelSize: 12
wrapMode: Text.Wrap
text: root.hasMetadata ? qsTr("A definition account, an initial holding, and a metadata account.") : qsTr("A definition account and an initial holding.")
}
}
}
Button {
id: createAccountsButton
objectName: "tokenCreateAccountsButton"
Layout.fillWidth: true
Layout.preferredHeight: 38
enabled: root.backend !== null && root.backend.isWalletOpen && !root.accountBusy
text: root.accountBusy ? qsTr("Creating fresh accounts…") : qsTr("Create fresh wallet accounts")
Accessible.name: qsTr("Create fresh wallet accounts for this definition")
onClicked: root.createTargetAccounts()
contentItem: Text {
color: parent.enabled ? "#F2D8C7" : "#8E8780"
font.pixelSize: 13
font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: createAccountsButton.text
}
background: Rectangle {
radius: 7
color: parent.enabled ? "#211914" : "#282522"
border.color: parent.enabled ? "#49301F" : "#3C3833"
border.width: 1
}
}
Text {
color: "#A9A098"
font.pixelSize: 12
text: qsTr("Definition target account")
}
TextField {
id: definitionTargetField
objectName: "tokenDefinitionTargetField"
Layout.fillWidth: true
Layout.preferredHeight: 44
Accessible.name: qsTr("Definition target account ID")
clip: true
color: "#E7E1D8"
font.family: "monospace"
font.pixelSize: 13
placeholderText: qsTr("New authorized account ID")
placeholderTextColor: "#8E8780"
selectByMouse: true
onTextEdited: root.clearDraftState()
background: Rectangle {
radius: 7
color: "#101010"
border.color: definitionTargetField.activeFocus ? "#F26A21" : definitionTargetField.text.length > 0 && !root.validDefinitionTarget ? "#D85F4B" : "#343434"
border.width: 1
}
}
Text {
Layout.fillWidth: true
color: definitionTargetField.text.length === 0 || root.validDefinitionTarget ? "#A9A098" : "#F08A76"
font.pixelSize: 12
text: definitionTargetField.text.length === 0 || root.validDefinitionTarget ? qsTr("Fresh public wallet account; accepts base58 or 64-character hex.") : qsTr("Enter a base58 account ID or 64-character hex account ID.")
}
Text {
color: "#A9A098"
font.pixelSize: 12
text: root.isFungible ? qsTr("Initial fungible holding target") : qsTr("NFT master holding target")
}
TextField {
id: holdingTargetField
objectName: "tokenHoldingTargetField"
Layout.fillWidth: true
Layout.preferredHeight: 44
Accessible.name: root.isFungible ? qsTr("Initial fungible holding target account ID") : qsTr("NFT master holding target account ID")
clip: true
color: "#E7E1D8"
font.family: "monospace"
font.pixelSize: 13
placeholderText: qsTr("New authorized account ID")
placeholderTextColor: "#8E8780"
selectByMouse: true
onTextEdited: root.clearDraftState()
background: Rectangle {
radius: 7
color: "#101010"
border.color: holdingTargetField.activeFocus ? "#F26A21" : holdingTargetField.text.length > 0 && !root.validHoldingTarget ? "#D85F4B" : "#343434"
border.width: 1
}
}
Text {
Layout.fillWidth: true
color: holdingTargetField.text.length === 0 || root.validHoldingTarget ? "#A9A098" : "#F08A76"
font.pixelSize: 12
text: holdingTargetField.text.length === 0 || root.validHoldingTarget ? qsTr("Fresh public wallet account; receives the initial state.") : qsTr("Enter a base58 account ID or 64-character hex account ID.")
}
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")
}
TextField {
id: metadataTargetField
objectName: "tokenMetadataTargetField"
Layout.fillWidth: true
Layout.preferredHeight: 44
Accessible.name: qsTr("Metadata target account ID")
clip: true
color: "#E7E1D8"
font.family: "monospace"
font.pixelSize: 13
placeholderText: qsTr("New authorized account ID")
placeholderTextColor: "#8E8780"
selectByMouse: true
onTextEdited: root.clearDraftState()
background: Rectangle {
radius: 7
color: "#101010"
border.color: metadataTargetField.activeFocus ? "#F26A21" : metadataTargetField.text.length > 0 && !root.validMetadataTarget ? "#D85F4B" : "#343434"
border.width: 1
}
}
Text {
Layout.fillWidth: true
color: metadataTargetField.text.length === 0 || root.validMetadataTarget ? "#A9A098" : "#F08A76"
font.pixelSize: 12
text: metadataTargetField.text.length === 0 || root.validMetadataTarget ? qsTr("Fresh public wallet account; stores the metadata record.") : qsTr("Enter a base58 account ID or 64-character hex account ID.")
}
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Button {
id: backToConfigureButton
objectName: "tokenBackToConfigureButton"
Layout.preferredWidth: 112
Layout.preferredHeight: 46
activeFocusOnTab: true
Accessible.name: qsTr("Back to token settings")
text: qsTr("Back")
onClicked: root.step = 0
contentItem: Text {
color: "#E7E1D8"
font.pixelSize: 14
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: backToConfigureButton.text
}
background: Rectangle {
radius: 8
color: "#101010"
border.color: backToConfigureButton.activeFocus ? "#FFB26B" : "#343434"
border.width: backToConfigureButton.activeFocus ? 2 : 1
}
}
Button {
id: reviewButton
objectName: "tokenReviewButton"
Layout.fillWidth: true
Layout.preferredHeight: 46
activeFocusOnTab: true
Accessible.name: qsTr("Review token definition")
enabled: root.canPrepare
text: root.canPrepare ? qsTr("Review definition") : qsTr("Complete targets")
onClicked: root.step = 2
contentItem: Text {
color: reviewButton.enabled ? "#FFFFFF" : "#8E8780"
elide: Text.ElideRight
font.pixelSize: 15
font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: reviewButton.text
}
background: Rectangle {
radius: 8
color: reviewButton.enabled ? "#F26A21" : "#282522"
border.color: reviewButton.activeFocus ? "#FFB26B" : reviewButton.enabled ? "#F26A21" : "#3C3833"
border.width: reviewButton.activeFocus ? 2 : 1
}
}
}
}
ColumnLayout {
id: reviewView
Layout.fillWidth: true
Layout.preferredHeight: visible ? implicitHeight : 0
spacing: 12
visible: root.step === 2
Rectangle {
Layout.fillWidth: true
implicitHeight: reviewContent.implicitHeight + 24
radius: 10
color: "#101010"
border.color: "#343434"
border.width: 1
ColumnLayout {
id: reviewContent
anchors.fill: parent
anchors.margins: 12
spacing: 9
RowLayout {
Layout.fillWidth: true
Rectangle {
Layout.preferredWidth: 78
Layout.preferredHeight: 28
radius: 14
color: root.isFungible ? "#211914" : "#182534"
border.color: root.isFungible ? "#6A4329" : "#40607A"
border.width: 1
Text {
anchors.centerIn: parent
color: root.isFungible ? "#F2D8C7" : "#BFD8F4"
font.pixelSize: 12
font.weight: Font.DemiBold
text: root.isFungible ? qsTr("Fungible") : qsTr("NFT")
}
}
Text {
Layout.fillWidth: true
color: "#E7E1D8"
elide: Text.ElideRight
font.pixelSize: 18
font.weight: Font.DemiBold
text: nameField.text.length > 0 ? nameField.text : qsTr("Untitled definition")
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: "#303030"
}
RowLayout {
Layout.fillWidth: true
Text {
color: "#A9A098"
font.pixelSize: 13
text: root.supplyLabel
}
Item {
Layout.fillWidth: true
}
Text {
color: root.validSupply ? "#E7E1D8" : "#F08A76"
font.pixelSize: 14
font.weight: Font.Medium
text: root.supplyValue
}
}
RowLayout {
Layout.fillWidth: true
Text {
color: "#A9A098"
font.pixelSize: 13
text: root.isFungible ? qsTr("Mint authority") : qsTr("Printing")
}
Item {
Layout.fillWidth: true
}
Text {
Layout.maximumWidth: 280
color: "#E7E1D8"
elide: Text.ElideRight
font.pixelSize: 13
text: root.authorityValue
horizontalAlignment: Text.AlignRight
}
}
RowLayout {
Layout.fillWidth: true
Text {
color: "#A9A098"
font.pixelSize: 13
text: qsTr("Metadata")
}
Item {
Layout.fillWidth: true
}
Text {
color: "#E7E1D8"
font.pixelSize: 13
text: root.hasMetadata ? root.metadataStandard === 0 ? qsTr("Simple") : qsTr("Expanded") : qsTr("None")
}
}
}
}
Text {
color: "#A9A098"
font.pixelSize: 12
text: qsTr("Account targets")
}
ColumnLayout {
Layout.fillWidth: true
spacing: 6
Repeater {
model: [
{ label: qsTr("Definition"), value: definitionTargetField.text, ok: root.validDefinitionTarget, metadata: false },
{ label: root.isFungible ? qsTr("Initial holding") : qsTr("Master holding"), value: holdingTargetField.text, ok: root.validHoldingTarget, metadata: false },
{ label: qsTr("Metadata"), value: metadataTargetField.text, ok: root.validMetadataTarget, metadata: true }
]
delegate: RowLayout {
id: reviewTargetRow
required property var modelData
Layout.fillWidth: true
visible: !reviewTargetRow.modelData.metadata || root.hasMetadata
spacing: 8
Text {
Layout.preferredWidth: 112
color: "#A9A098"
font.pixelSize: 12
text: reviewTargetRow.modelData.label
}
Text {
Layout.fillWidth: true
color: "#E7E1D8"
elide: Text.ElideMiddle
font.family: "monospace"
font.pixelSize: 12
text: reviewTargetRow.modelData.value.length > 0 ? reviewTargetRow.modelData.value : qsTr("Missing")
}
Text {
color: reviewTargetRow.modelData.value.length > 0 && reviewTargetRow.modelData.ok ? "#78C88D" : "#F08A76"
font.pixelSize: 12
text: reviewTargetRow.modelData.value.length > 0 && reviewTargetRow.modelData.ok ? qsTr("Ready") : qsTr("Needs input")
}
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: reviewNotice.implicitHeight + 20
radius: 8
color: "#211914"
border.color: "#49301F"
border.width: 1
Text {
id: reviewNotice
anchors.fill: parent
anchors.margins: 10
color: "#F2D8C7"
font.pixelSize: 12
wrapMode: Text.Wrap
text: root.hasMetadata ? qsTr("Metadata-backed definitions use typed serialization.") : qsTr("Fungible definitions use the standard Token Program fields.")
}
}
Text {
Layout.fillWidth: true
visible: root.prepared || root.errorMessage.length > 0
color: root.errorMessage.length > 0 ? "#F08A76" : "#78C88D"
font.pixelSize: 13
wrapMode: Text.Wrap
text: root.errorMessage.length > 0 ? root.errorMessage : root.preparedMessage
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Button {
id: backToAccountsButton
objectName: "tokenBackToAccountsButton"
Layout.preferredWidth: 112
Layout.preferredHeight: 46
activeFocusOnTab: true
Accessible.name: qsTr("Back to account targets")
enabled: !root.prepared && !root.submitting
text: qsTr("Back")
onClicked: root.step = 1
contentItem: Text {
color: backToAccountsButton.enabled ? "#E7E1D8" : "#8E8780"
font.pixelSize: 14
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: backToAccountsButton.text
}
background: Rectangle {
radius: 8
color: "#101010"
border.color: backToAccountsButton.activeFocus ? "#FFB26B" : "#343434"
border.width: backToAccountsButton.activeFocus ? 2 : 1
}
}
Button {
id: prepareButton
objectName: "tokenPrepareButton"
Layout.fillWidth: true
Layout.preferredHeight: 46
activeFocusOnTab: true
Accessible.name: qsTr("Create token definition")
enabled: root.canSubmit
text: root.submitting ? qsTr("Submitting…") : root.prepared ? qsTr("Transaction submitted") : !root.backend || !root.backend.isWalletOpen ? qsTr("Connect wallet to create") : root.canPrepare ? qsTr("Create token definition") : qsTr("Complete required fields")
onClicked: root.prepareDefinition()
contentItem: Text {
color: prepareButton.enabled ? "#FFFFFF" : root.prepared ? "#78C88D" : "#8E8780"
font.pixelSize: 15
font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: prepareButton.text
}
background: Rectangle {
radius: 8
color: prepareButton.enabled ? "#F26A21" : root.prepared ? "#183222" : "#282522"
border.color: prepareButton.activeFocus ? "#FFB26B" : prepareButton.enabled ? "#F26A21" : root.prepared ? "#39C06A" : "#3C3833"
border.width: prepareButton.activeFocus ? 2 : 1
}
}
}
Button {
id: inspectButton
objectName: "tokenInspectButton"
Layout.fillWidth: true
Layout.preferredHeight: 42
visible: root.prepared
activeFocusOnTab: true
Accessible.name: qsTr("Inspect token definition")
text: qsTr("Open in Inspect")
onClicked: root.requestInspect()
contentItem: Text {
color: "#F2D8C7"
font.pixelSize: 14
font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: inspectButton.text
}
background: Rectangle {
radius: 8
color: "#211914"
border.color: inspectButton.activeFocus ? "#FFB26B" : "#49301F"
border.width: inspectButton.activeFocus ? 2 : 1
}
}
}
}
}
Rectangle {
id: summaryPanel
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.minimumWidth: 0
Layout.preferredWidth: stage.columnCount === 2 ? 300 : 0
Layout.maximumWidth: 1120
implicitHeight: summaryContent.implicitHeight + 32
radius: 16
color: "#181818"
border.color: "#303030"
border.width: 1
ColumnLayout {
id: summaryContent
anchors.fill: parent
anchors.margins: 16
spacing: 12
Text {
color: "#E7E1D8"
font.pixelSize: 18
font.weight: Font.DemiBold
text: qsTr("Definition summary")
}
Rectangle {
Layout.fillWidth: true
implicitHeight: summaryHeadline.implicitHeight + 20
radius: 10
color: root.isFungible ? "#211914" : "#182534"
border.color: root.isFungible ? "#49301F" : "#31435D"
border.width: 1
ColumnLayout {
id: summaryHeadline
anchors.fill: parent
anchors.margins: 10
spacing: 5
Text {
color: root.isFungible ? "#F2D8C7" : "#BFD8F4"
font.pixelSize: 12
font.weight: Font.DemiBold
text: root.isFungible ? qsTr("Fungible") : qsTr("Non-fungible")
}
Text {
Layout.fillWidth: true
color: "#E7E1D8"
elide: Text.ElideRight
font.pixelSize: 18
font.weight: Font.DemiBold
text: nameField.text.length > 0 ? nameField.text : qsTr("Untitled definition")
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 8
RowLayout {
Layout.fillWidth: true
Text {
color: "#A9A098"
font.pixelSize: 12
text: root.supplyLabel
}
Item {
Layout.fillWidth: true
}
Text {
color: root.validSupply ? "#E7E1D8" : "#F08A76"
font.pixelSize: 13
font.weight: Font.Medium
text: root.supplyValue
}
}
RowLayout {
Layout.fillWidth: true
Text {
color: "#A9A098"
font.pixelSize: 12
text: root.isFungible ? qsTr("Authority") : qsTr("Printing")
}
Item {
Layout.fillWidth: true
}
Text {
Layout.maximumWidth: 170
color: "#E7E1D8"
elide: Text.ElideRight
font.pixelSize: 12
text: root.authorityValue
horizontalAlignment: Text.AlignRight
}
}
RowLayout {
Layout.fillWidth: true
Text {
color: "#A9A098"
font.pixelSize: 12
text: qsTr("Metadata")
}
Item {
Layout.fillWidth: true
}
Text {
color: "#E7E1D8"
font.pixelSize: 12
text: root.hasMetadata ? root.metadataStandard === 0 ? qsTr("Simple") : qsTr("Expanded") : qsTr("Not included")
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: "#303030"
}
Text {
color: "#E7E1D8"
font.pixelSize: 14
font.weight: Font.DemiBold
text: qsTr("Readiness")
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Rectangle {
Layout.preferredWidth: 28
Layout.preferredHeight: 28
radius: 14
color: root.validTargetCount === root.targetCount ? "#183222" : "#332521"
border.color: root.validTargetCount === root.targetCount ? "#39C06A" : "#D85F4B"
border.width: 1
Text {
anchors.centerIn: parent
color: root.validTargetCount === root.targetCount ? "#78C88D" : "#F08A76"
font.pixelSize: 12
font.weight: Font.DemiBold
text: root.validTargetCount === root.targetCount ? qsTr("OK") : qsTr("!")
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
Text {
color: "#E7E1D8"
font.pixelSize: 13
font.weight: Font.Medium
text: root.validTargetCount === root.targetCount ? qsTr("All targets look valid") : qsTr("%1 of %2 targets ready").arg(root.validTargetCount).arg(root.targetCount)
}
Text {
Layout.fillWidth: true
color: "#A9A098"
font.pixelSize: 12
wrapMode: Text.Wrap
text: root.step === 0 ? qsTr("Account targets appear next.") : root.step === 1 ? qsTr("Review unlocks when every target is valid.") : root.prepared ? qsTr("Transaction submitted to the Token Program.") : qsTr("Ready for final review.")
}
}
}
Button {
id: summaryContinueButton
objectName: "tokenSummaryContinueButton"
Layout.fillWidth: true
Layout.preferredHeight: visible ? 46 : 0
visible: root.step === 0 && stage.columnCount === 2
activeFocusOnTab: true
Accessible.name: qsTr("Continue to account targets")
enabled: root.canContinue
text: root.canContinue ? qsTr("Continue to account targets") : qsTr("Complete required fields")
onClicked: root.step = 1
contentItem: Text {
color: summaryContinueButton.enabled ? "#FFFFFF" : "#8E8780"
font.pixelSize: 14
font.weight: Font.DemiBold
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: summaryContinueButton.text
}
background: Rectangle {
radius: 8
color: summaryContinueButton.enabled ? "#F26A21" : "#282522"
border.color: summaryContinueButton.activeFocus ? "#FFB26B" : summaryContinueButton.enabled ? "#F26A21" : "#3C3833"
border.width: summaryContinueButton.activeFocus ? 2 : 1
}
}
}
}
}
}
}
}