many fixes
This commit is contained in:
parent
7286741c41
commit
a4d04a60ae
|
|
@ -551,6 +551,7 @@ dependencies = [
|
|||
"shellexpand",
|
||||
"skein",
|
||||
"sled",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tokio-postgres",
|
||||
"windows-service",
|
||||
|
|
@ -2077,7 +2078,7 @@ dependencies = [
|
|||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.6.4",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
|
|
@ -3528,7 +3529,7 @@ dependencies = [
|
|||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"socket2 0.6.4",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
@ -3565,7 +3566,7 @@ dependencies = [
|
|||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"socket2 0.6.4",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
|
@ -4456,6 +4457,16 @@ version = "1.15.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.4"
|
||||
|
|
@ -5080,7 +5091,7 @@ dependencies = [
|
|||
"parking_lot 0.12.5",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"socket2 0.6.4",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
|
@ -5126,7 +5137,7 @@ dependencies = [
|
|||
"postgres-protocol",
|
||||
"postgres-types",
|
||||
"rand 0.10.1",
|
||||
"socket2",
|
||||
"socket2 0.6.4",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"whoami",
|
||||
|
|
|
|||
|
|
@ -391,8 +391,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions" data-transfer-actions>
|
||||
<button class="secondary-button" type="button" data-transfer-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-transfer-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-transfer-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-transfer-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message" data-transfer-message></div>
|
||||
|
||||
|
|
@ -420,8 +420,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions hidden" data-create-token-actions>
|
||||
<button class="secondary-button" type="button" data-create-token-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-create-token-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-create-token-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-create-token-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message hidden" data-create-token-message></div>
|
||||
|
||||
|
|
@ -447,8 +447,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions hidden" data-issue-token-actions>
|
||||
<button class="secondary-button" type="button" data-issue-token-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-issue-token-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-issue-token-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-issue-token-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message hidden" data-issue-token-message></div>
|
||||
|
||||
|
|
@ -467,6 +467,13 @@
|
|||
<span>NFT Name</span>
|
||||
<input data-create-nft-name maxlength="15" placeholder="3 to 15 characters">
|
||||
</label>
|
||||
<label>
|
||||
<span>Ownership</span>
|
||||
<select data-create-nft-ownership>
|
||||
<option value="0">Indivisible</option>
|
||||
<option value="1">Fractional</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>IPFS CID</span>
|
||||
<input data-create-nft-ipfs maxlength="100" placeholder="CIDv0 or CIDv1">
|
||||
|
|
@ -491,8 +498,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions hidden" data-create-nft-actions>
|
||||
<button class="secondary-button" type="button" data-create-nft-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-create-nft-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-create-nft-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-create-nft-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message hidden" data-create-nft-message></div>
|
||||
|
||||
|
|
@ -675,8 +682,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions hidden" data-loan-payment-actions>
|
||||
<button class="secondary-button" type="button" data-loan-payment-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-loan-payment-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-loan-payment-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-loan-payment-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message hidden" data-loan-payment-message></div>
|
||||
|
||||
|
|
@ -703,8 +710,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions hidden" data-collateral-claim-actions>
|
||||
<button class="secondary-button" type="button" data-collateral-claim-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-collateral-claim-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-collateral-claim-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-collateral-claim-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message hidden" data-collateral-claim-message></div>
|
||||
|
||||
|
|
@ -729,8 +736,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions hidden" data-burn-actions>
|
||||
<button class="secondary-button" type="button" data-burn-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-burn-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-burn-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-burn-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message hidden" data-burn-message></div>
|
||||
|
||||
|
|
@ -781,8 +788,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions hidden" data-marketing-actions>
|
||||
<button class="secondary-button" type="button" data-marketing-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-marketing-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-marketing-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-marketing-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message hidden" data-marketing-message></div>
|
||||
|
||||
|
|
@ -833,8 +840,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions hidden" data-data-storage-actions>
|
||||
<button class="secondary-button" type="button" data-data-storage-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-data-storage-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-data-storage-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-data-storage-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message hidden" data-data-storage-message></div>
|
||||
|
||||
|
|
@ -857,8 +864,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-actions hidden" data-vanity-actions>
|
||||
<button class="secondary-button" type="button" data-vanity-save>Save Transaction</button>
|
||||
<button class="primary-button" type="button" data-vanity-broadcast>Broadcast Transaction</button>
|
||||
<button class="secondary-button" type="button" data-vanity-save>Sign and Save</button>
|
||||
<button class="primary-button" type="button" data-vanity-broadcast>Sign and Broadcast</button>
|
||||
</div>
|
||||
<div class="startup-message hidden" data-vanity-message></div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ const issueTokenMessage = document.querySelector("[data-issue-token-message]");
|
|||
const createNftForm = document.querySelector("[data-create-nft-form]");
|
||||
const createNftActions = document.querySelector("[data-create-nft-actions]");
|
||||
const createNftSeriesSelect = document.querySelector("[data-create-nft-series]");
|
||||
const createNftOwnershipSelect = document.querySelector("[data-create-nft-ownership]");
|
||||
const createNftNameInput = document.querySelector("[data-create-nft-name]");
|
||||
const createNftIpfsInput = document.querySelector("[data-create-nft-ipfs]");
|
||||
const createNftCountInput = document.querySelector("[data-create-nft-count]");
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ function selectedBurnAsset() {
|
|||
return {
|
||||
asset: option.dataset.asset || "",
|
||||
nftSeries: Number(option.dataset.nftSeries || 0),
|
||||
ownershipType: Number(option.dataset.ownershipType || -1),
|
||||
isNft: option.dataset.isNft === "true",
|
||||
balance: BigInt(option.dataset.balance || "0")
|
||||
};
|
||||
}
|
||||
|
|
@ -55,6 +57,9 @@ function populateBurnAssets() {
|
|||
option.dataset.asset = balance.asset || "";
|
||||
option.dataset.nftSeries = String(balance.nft_series || 0);
|
||||
option.dataset.balance = String(balance.balance || "0");
|
||||
const metadata = nftOwnershipMetadata(balance.asset, balance.nft_series);
|
||||
option.dataset.isNft = String(Boolean(metadata));
|
||||
option.dataset.ownershipType = String(metadata?.ownershipType ?? -1);
|
||||
option.textContent = burnAssetOptionLabel(balance);
|
||||
burnAssetSelect.appendChild(option);
|
||||
});
|
||||
|
|
@ -70,7 +75,7 @@ function populateBurnAssets() {
|
|||
}
|
||||
|
||||
async function ensureBurnBalances() {
|
||||
if (activeWalletBalances.length || !walletLoaded || !registrationRegistered) {
|
||||
if (!walletLoaded || !registrationRegistered) {
|
||||
return;
|
||||
}
|
||||
const invoke = window.__TAURI__?.core?.invoke;
|
||||
|
|
@ -78,8 +83,13 @@ async function ensureBurnBalances() {
|
|||
if (!invoke || !broadcastNode) {
|
||||
return;
|
||||
}
|
||||
burnBalancesRefreshPromise ||= invoke("get_active_wallet_balances", { broadcastNode })
|
||||
.then((balances) => {
|
||||
burnBalancesRefreshPromise ||= Promise.all([
|
||||
activeWalletBalances.length
|
||||
? Promise.resolve(activeWalletBalances)
|
||||
: invoke("get_active_wallet_balances", { broadcastNode }),
|
||||
ensureNftOwnershipRegistry()
|
||||
])
|
||||
.then(([balances]) => {
|
||||
activeWalletBalances = Array.isArray(balances) ? balances : [];
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
@ -113,16 +123,19 @@ function updateBurnDerivedFields() {
|
|||
return;
|
||||
}
|
||||
|
||||
const isNft = selected.nftSeries > 0;
|
||||
const isNft = selected.isNft;
|
||||
const isIndivisible = isNft && selected.ownershipType === 0;
|
||||
if (burnAvailable) {
|
||||
burnAvailable.textContent = `${atomicToDecimal(selected.balance)} ${selected.asset}`;
|
||||
}
|
||||
if (burnType) {
|
||||
burnType.textContent = isNft ? `NFT #${selected.nftSeries}` : "Token";
|
||||
burnType.textContent = isNft
|
||||
? (selected.nftSeries > 0 ? `NFT #${selected.nftSeries}` : "NFT / RWA")
|
||||
: "Token";
|
||||
}
|
||||
if (burnAmountInput) {
|
||||
burnAmountInput.disabled = isNft;
|
||||
if (isNft) {
|
||||
burnAmountInput.disabled = isIndivisible;
|
||||
if (isIndivisible) {
|
||||
burnAmountInput.value = "1.00000000";
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +156,7 @@ function validateBurnForm() {
|
|||
if (amount === null || amount <= 0n) {
|
||||
return "Enter a valid burn amount.";
|
||||
}
|
||||
if (selected.nftSeries > 0 && amount !== ATOMIC_UNITS_PER_COIN) {
|
||||
if (selected.isNft && selected.ownershipType === 0 && amount !== ATOMIC_UNITS_PER_COIN) {
|
||||
return "NFT burns must destroy exactly 1.00000000 units.";
|
||||
}
|
||||
if (amount > selected.balance) {
|
||||
|
|
@ -209,7 +222,7 @@ async function handleBurnAction(action) {
|
|||
setBurnButtonsDisabled(true);
|
||||
try {
|
||||
if (action === "save") {
|
||||
setBurnMessage("Saving burn transaction...");
|
||||
setBurnMessage("Signing and saving burn transaction...");
|
||||
const review = await invoke("save_burn_transaction", {
|
||||
...burnPayload(),
|
||||
walletKey
|
||||
|
|
@ -224,7 +237,7 @@ async function handleBurnAction(action) {
|
|||
if (!broadcastNode) {
|
||||
throw new Error("Select a broadcast node before broadcasting.");
|
||||
}
|
||||
setBurnMessage("Broadcasting burn transaction...");
|
||||
setBurnMessage("Signing and broadcasting burn transaction...");
|
||||
const result = await invoke("broadcast_burn_transaction", {
|
||||
...burnPayload(),
|
||||
broadcastNode,
|
||||
|
|
@ -233,6 +246,12 @@ async function handleBurnAction(action) {
|
|||
if (typeof invalidateAssetsCatalog === "function") {
|
||||
invalidateAssetsCatalog();
|
||||
}
|
||||
if (typeof invalidateDashboardNftBalanceKeys === "function") {
|
||||
invalidateDashboardNftBalanceKeys();
|
||||
}
|
||||
if (typeof resetNftsPage === "function") {
|
||||
resetNftsPage();
|
||||
}
|
||||
activeWalletBalances = [];
|
||||
clearBurnForm();
|
||||
setBurnMessage(result.response || "Burn transaction broadcast.", "success");
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ async function handleCollateralClaimAction(action) {
|
|||
setCollateralClaimButtonsDisabled(true);
|
||||
try {
|
||||
if (action === "save") {
|
||||
setCollateralClaimMessage("Saving collateral claim transaction...");
|
||||
setCollateralClaimMessage("Signing and saving collateral claim transaction...");
|
||||
const review = await invoke("save_collateral_claim_transaction", {
|
||||
...collateralClaimPayload(),
|
||||
walletKey
|
||||
|
|
@ -178,7 +178,7 @@ async function handleCollateralClaimAction(action) {
|
|||
refreshUnbroadcastTransactions();
|
||||
}
|
||||
} else {
|
||||
setCollateralClaimMessage("Broadcasting collateral claim...");
|
||||
setCollateralClaimMessage("Signing and broadcasting collateral claim...");
|
||||
const result = await invoke("broadcast_collateral_claim_transaction", {
|
||||
...collateralClaimPayload(),
|
||||
walletKey
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ function createNftSeriesValue() {
|
|||
return Number(createNftSeriesSelect?.value || 0) === 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
function createNftOwnershipValue() {
|
||||
return Number(createNftOwnershipSelect?.value || 0) === 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
function normalizeCreateNftCountInput() {
|
||||
const isCollection = createNftSeriesValue() === 1;
|
||||
if (createNftCountInput) {
|
||||
|
|
@ -20,6 +24,12 @@ function normalizeCreateNftCountInput() {
|
|||
createNftCountInput.value = "2";
|
||||
}
|
||||
}
|
||||
if (createNftOwnershipSelect) {
|
||||
createNftOwnershipSelect.disabled = isCollection;
|
||||
if (isCollection) {
|
||||
createNftOwnershipSelect.value = "0";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateCreateNftDerivedFields({ clampCount = false } = {}) {
|
||||
|
|
@ -108,6 +118,7 @@ function validateCreateNftForm() {
|
|||
function createNftPayload() {
|
||||
return {
|
||||
series: createNftSeriesValue(),
|
||||
ownershipType: createNftOwnershipValue(),
|
||||
nftName: String(createNftNameInput?.value || "").trim(),
|
||||
itemIpfs: String(createNftIpfsInput?.value || "").trim(),
|
||||
count: Number.parseInt(createNftCountInput?.value || "1", 10),
|
||||
|
|
@ -120,6 +131,9 @@ function clearCreateNftForm() {
|
|||
if (createNftSeriesSelect) {
|
||||
createNftSeriesSelect.value = "0";
|
||||
}
|
||||
if (createNftOwnershipSelect) {
|
||||
createNftOwnershipSelect.value = "0";
|
||||
}
|
||||
if (createNftNameInput) {
|
||||
createNftNameInput.value = "";
|
||||
}
|
||||
|
|
@ -181,7 +195,7 @@ async function handleCreateNftAction(action) {
|
|||
setCreateNftButtonsDisabled(true);
|
||||
try {
|
||||
if (action === "save") {
|
||||
setCreateNftMessage("Saving NFT transaction...");
|
||||
setCreateNftMessage("Signing and saving NFT transaction...");
|
||||
const review = await invoke("save_create_nft_transaction", {
|
||||
...createNftPayload(),
|
||||
walletKey
|
||||
|
|
@ -196,7 +210,7 @@ async function handleCreateNftAction(action) {
|
|||
if (!broadcastNode) {
|
||||
throw new Error("Select a broadcast node before broadcasting.");
|
||||
}
|
||||
setCreateNftMessage("Broadcasting NFT transaction...");
|
||||
setCreateNftMessage("Signing and broadcasting NFT transaction...");
|
||||
const result = await invoke("broadcast_create_nft_transaction", {
|
||||
...createNftPayload(),
|
||||
broadcastNode,
|
||||
|
|
@ -230,6 +244,7 @@ createNftSeriesSelect?.addEventListener("change", () => {
|
|||
updateCreateNftDerivedFields({ clampCount: true });
|
||||
setCreateNftMessage("");
|
||||
});
|
||||
createNftOwnershipSelect?.addEventListener("change", () => setCreateNftMessage(""));
|
||||
createNftCountInput?.addEventListener("input", () => {
|
||||
updateCreateNftDerivedFields();
|
||||
setCreateNftMessage("");
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ async function handleCreateTokenAction(action) {
|
|||
setCreateTokenButtonsDisabled(true);
|
||||
try {
|
||||
if (action === "save") {
|
||||
setCreateTokenMessage("Saving token transaction...");
|
||||
setCreateTokenMessage("Signing and saving token transaction...");
|
||||
const review = await invoke("save_create_token_transaction", {
|
||||
...createTokenPayload(),
|
||||
walletKey
|
||||
|
|
@ -133,7 +133,7 @@ async function handleCreateTokenAction(action) {
|
|||
if (!broadcastNode) {
|
||||
throw new Error("Select a broadcast node before broadcasting.");
|
||||
}
|
||||
setCreateTokenMessage("Broadcasting token transaction...");
|
||||
setCreateTokenMessage("Signing and broadcasting token transaction...");
|
||||
const result = await invoke("broadcast_create_token_transaction", {
|
||||
...createTokenPayload(),
|
||||
broadcastNode,
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ async function handleDataStorageAction(action) {
|
|||
setDataStorageButtonsDisabled(true);
|
||||
try {
|
||||
if (action === "save") {
|
||||
setDataStorageMessage("Saving data storage transaction...");
|
||||
setDataStorageMessage("Signing and saving data storage transaction...");
|
||||
const review = await invoke("save_data_storage_transaction", {
|
||||
...dataStoragePayload(),
|
||||
walletKey
|
||||
|
|
@ -386,7 +386,7 @@ async function handleDataStorageAction(action) {
|
|||
if (!broadcastNode) {
|
||||
throw new Error("Select a broadcast node before broadcasting.");
|
||||
}
|
||||
setDataStorageMessage("Broadcasting data storage transaction...");
|
||||
setDataStorageMessage("Signing and broadcasting data storage transaction...");
|
||||
const result = await invoke("broadcast_data_storage_transaction", {
|
||||
...dataStoragePayload(),
|
||||
broadcastNode,
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ async function handleIssueTokenAction(action) {
|
|||
setIssueTokenButtonsDisabled(true);
|
||||
try {
|
||||
if (action === "save") {
|
||||
setIssueTokenMessage("Saving issue-token transaction...");
|
||||
setIssueTokenMessage("Signing and saving issue-token transaction...");
|
||||
const review = await invoke("save_issue_token_transaction", {
|
||||
...issueTokenPayload(),
|
||||
walletKey
|
||||
|
|
@ -233,7 +233,7 @@ async function handleIssueTokenAction(action) {
|
|||
refreshUnbroadcastTransactions();
|
||||
}
|
||||
} else {
|
||||
setIssueTokenMessage("Broadcasting issue-token transaction...");
|
||||
setIssueTokenMessage("Signing and broadcasting issue-token transaction...");
|
||||
const result = await invoke("broadcast_issue_token_transaction", {
|
||||
...issueTokenPayload(),
|
||||
walletKey
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ function selectedLoanCollateral() {
|
|||
asset: option.dataset.asset || "",
|
||||
kind: option.dataset.kind || "token",
|
||||
series: Number(option.dataset.series || 0),
|
||||
ownershipType: Number(option.dataset.ownershipType || -1),
|
||||
wireName: option.dataset.wireName || option.dataset.asset || ""
|
||||
};
|
||||
}
|
||||
|
|
@ -120,6 +121,7 @@ function populateLoanCollateralSelect() {
|
|||
option.dataset.asset = asset.asset;
|
||||
option.dataset.kind = asset.kind;
|
||||
option.dataset.series = String(asset.series || 0);
|
||||
option.dataset.ownershipType = String(asset.ownershipType ?? -1);
|
||||
option.dataset.wireName = asset.wireName;
|
||||
loanCollateralSelect.appendChild(option);
|
||||
});
|
||||
|
|
@ -167,6 +169,7 @@ async function loadLoanOfferAssets() {
|
|||
const tokenRows = Array.isArray(tokens) ? tokens : [];
|
||||
const nftRows = Array.isArray(nfts) ? nfts : [];
|
||||
loanNftKeys = new Set(nftRows.map((nft) => loanNftKey(nft.nft_name, nft.series)));
|
||||
updateNftOwnershipRegistry(nftRows, key);
|
||||
|
||||
loanCollateralAssets = [
|
||||
{
|
||||
|
|
@ -193,7 +196,10 @@ async function loadLoanOfferAssets() {
|
|||
wireName: series > 0 ? `${asset}_${series}` : asset,
|
||||
series,
|
||||
kind: "nft",
|
||||
label: series > 0 ? `${asset} #${series} (NFT)` : `${asset} (NFT)`
|
||||
ownershipType: Number(nft.ownership_type),
|
||||
label: series > 0
|
||||
? `${asset} #${series} (NFT)`
|
||||
: `${asset} (${Number(nft.ownership_type) === 1 ? "Fractional NFT / RWA" : "NFT"})`
|
||||
};
|
||||
})
|
||||
].filter((asset) => {
|
||||
|
|
@ -229,7 +235,7 @@ function updateLoanOfferDerivedFields() {
|
|||
if (loanFeeInput) {
|
||||
loanFeeInput.value = atomicToDecimal(LOAN_FEE_ATOMIC);
|
||||
}
|
||||
if (collateral?.kind === "nft") {
|
||||
if (collateral?.kind === "nft" && collateral.ownershipType === 0) {
|
||||
loanCollateralAmountInput.value = "1";
|
||||
loanCollateralAmountInput.disabled = true;
|
||||
} else if (loanCollateralAmountInput) {
|
||||
|
|
@ -273,9 +279,7 @@ function validateLoanOfferForm() {
|
|||
}
|
||||
|
||||
const loanAmount = decimalToAtomic(loanAmountInput?.value);
|
||||
const collateralAmount = collateral.kind === "nft"
|
||||
? ATOMIC_UNITS_PER_COIN
|
||||
: decimalToAtomic(loanCollateralAmountInput?.value);
|
||||
const collateralAmount = decimalToAtomic(loanCollateralAmountInput?.value);
|
||||
const paymentAmount = decimalToAtomic(loanPaymentAmountInput?.value);
|
||||
const maxLateValue = decimalToAtomic(loanMaxLateValueInput?.value);
|
||||
if (loanAmount === null || loanAmount <= 0n) {
|
||||
|
|
@ -284,6 +288,13 @@ function validateLoanOfferForm() {
|
|||
if (collateralAmount === null || collateralAmount <= 0n) {
|
||||
return "Enter a valid collateral amount.";
|
||||
}
|
||||
if (
|
||||
collateral.kind === "nft"
|
||||
&& collateral.ownershipType === 0
|
||||
&& collateralAmount !== ATOMIC_UNITS_PER_COIN
|
||||
) {
|
||||
return "Indivisible NFT collateral must be exactly 1.";
|
||||
}
|
||||
if (paymentAmount === null || paymentAmount <= 0n) {
|
||||
return "Enter a valid payment amount.";
|
||||
}
|
||||
|
|
@ -330,9 +341,7 @@ function validateLoanOfferForm() {
|
|||
function loanOfferPayload() {
|
||||
const loanAsset = selectedLoanAsset();
|
||||
const collateral = selectedLoanCollateral();
|
||||
const collateralAmount = collateral.kind === "nft"
|
||||
? ATOMIC_UNITS_PER_COIN
|
||||
: decimalToAtomic(loanCollateralAmountInput.value);
|
||||
const collateralAmount = decimalToAtomic(loanCollateralAmountInput.value);
|
||||
return {
|
||||
broadcastNode: activeBroadcastNode(),
|
||||
loanCoin: loanAsset.asset,
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ async function handleLoanPaymentAction(action) {
|
|||
setLoanPaymentButtonsDisabled(true);
|
||||
try {
|
||||
if (action === "save") {
|
||||
setLoanPaymentMessage("Saving loan payment transaction...");
|
||||
setLoanPaymentMessage("Signing and saving loan payment transaction...");
|
||||
const review = await invoke("save_loan_payment_transaction", {
|
||||
...loanPaymentPayload(),
|
||||
walletKey
|
||||
|
|
@ -238,7 +238,7 @@ async function handleLoanPaymentAction(action) {
|
|||
refreshUnbroadcastTransactions();
|
||||
}
|
||||
} else {
|
||||
setLoanPaymentMessage("Broadcasting loan payment...");
|
||||
setLoanPaymentMessage("Signing and broadcasting loan payment...");
|
||||
const result = await invoke("broadcast_loan_payment_transaction", {
|
||||
...loanPaymentPayload(),
|
||||
walletKey
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ async function handleMarketingAction(action) {
|
|||
setMarketingButtonsDisabled(true);
|
||||
try {
|
||||
if (action === "save") {
|
||||
setMarketingMessage("Saving marketing transaction...");
|
||||
setMarketingMessage("Signing and saving marketing transaction...");
|
||||
const review = await invoke("save_marketing_transaction", {
|
||||
...marketingPayload(),
|
||||
walletKey
|
||||
|
|
@ -141,7 +141,7 @@ async function handleMarketingAction(action) {
|
|||
if (!broadcastNode) {
|
||||
throw new Error("Select a broadcast node before broadcasting.");
|
||||
}
|
||||
setMarketingMessage("Broadcasting marketing transaction...");
|
||||
setMarketingMessage("Signing and broadcasting marketing transaction...");
|
||||
const result = await invoke("broadcast_marketing_transaction", {
|
||||
...marketingPayload(),
|
||||
broadcastNode,
|
||||
|
|
|
|||
|
|
@ -165,7 +165,9 @@ function ownedNftRowsFromBalances(balances, registry) {
|
|||
nft_name: nftName,
|
||||
series,
|
||||
balance: String(balance.balance || "0"),
|
||||
origin_txid: nft.origin_txid || ""
|
||||
origin_txid: nft.origin_txid || "",
|
||||
ownership_type: Number(nft.ownership_type || 0),
|
||||
remaining_supply: String(nft.remaining_supply || "0")
|
||||
};
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
|
@ -197,12 +199,29 @@ function metadataDescription(metadata) {
|
|||
return String(metadata?.description || metadata?.summary || "").trim();
|
||||
}
|
||||
|
||||
function nftOwnershipPercent(balance, remainingSupply) {
|
||||
const atomic = BigInt(String(balance || "0"));
|
||||
const supply = BigInt(String(remainingSupply || "0"));
|
||||
if (supply <= 0n) {
|
||||
return "0%";
|
||||
}
|
||||
const scaledPercent = atomic * 100000000n / supply;
|
||||
const whole = scaledPercent / 1000000n;
|
||||
const fraction = String(scaledPercent % 1000000n).padStart(6, "0").replace(/0+$/, "");
|
||||
return fraction ? `${whole}.${fraction}%` : `${whole}%`;
|
||||
}
|
||||
|
||||
function renderNftCard(nft) {
|
||||
const key = nftKey(nft);
|
||||
const name = nftDisplayName(nft);
|
||||
const image = nftImageUri(nft);
|
||||
const type = nftTypeLabel(nft);
|
||||
const active = key === selectedNftKey ? " active" : "";
|
||||
const fractional = Number(nft.ownership_type || 0) === 1;
|
||||
const ownershipAtomic = BigInt(String(nft.balance || "0"));
|
||||
const ownershipLabel = fractional
|
||||
? `${nftOwnershipPercent(ownershipAtomic, nft.remaining_supply)} ownership`
|
||||
: "Complete ownership";
|
||||
const preview = image && nft.gateway
|
||||
? `<img src="${escapeNftText(ipfsToGatewayUrl(image, nft.gateway))}" alt="">`
|
||||
: `<div class="asset-mark art">${escapeNftText(assetInitial(name))}</div>`;
|
||||
|
|
@ -214,6 +233,7 @@ function renderNftCard(nft) {
|
|||
<div>
|
||||
<strong>${escapeNftText(name)}</strong>
|
||||
<span>${escapeNftText(type)} - ${escapeNftText(nft.asset_name || `${nft.nft_name}_${nft.series}`)}</span>
|
||||
<span>${escapeNftText(ownershipLabel)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nft-link-row">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const SWAP_FEE_ATOMIC = 100000000n;
|
||||
let swapOfferLoadPromise = null;
|
||||
let swapNetworkAssets = [];
|
||||
let swapNftKeys = new Set();
|
||||
let swapNftMetadata = new Map();
|
||||
let swapOfferAssetsKey = "";
|
||||
|
||||
function setSwapOfferMessage(message, type = "") {
|
||||
|
|
@ -18,10 +18,14 @@ function swapAssetLabel(asset, includeBalance = false) {
|
|||
? `${asset.asset} #${series}`
|
||||
: asset.asset;
|
||||
if (!includeBalance) {
|
||||
return asset.kind === "nft" ? `${name} (NFT)` : name;
|
||||
return asset.kind === "nft"
|
||||
? `${name} (${asset.ownershipType === 1 ? "Fractional NFT / RWA" : "NFT"})`
|
||||
: name;
|
||||
}
|
||||
if (asset.kind === "nft") {
|
||||
return `${name} (1 item)`;
|
||||
return asset.ownershipType === 1
|
||||
? `${name} (${formatAtomicBalance(String(asset.balance || "0"))})`
|
||||
: `${name} (1 item)`;
|
||||
}
|
||||
return `${name} (${formatAtomicBalance(String(asset.balance || "0"))})`;
|
||||
}
|
||||
|
|
@ -35,6 +39,7 @@ function selectedSwapAsset(select) {
|
|||
asset: option.dataset.asset || "",
|
||||
nftSeries: Number(option.dataset.nftSeries || 0),
|
||||
kind: option.dataset.kind || "token",
|
||||
ownershipType: Number(option.dataset.ownershipType || -1),
|
||||
balance: BigInt(option.dataset.balance || "0")
|
||||
};
|
||||
}
|
||||
|
|
@ -59,6 +64,7 @@ function populateSwapAssetSelect(select, assets, includeBalance) {
|
|||
option.dataset.asset = asset.asset;
|
||||
option.dataset.nftSeries = String(Number(asset.nftSeries || 0));
|
||||
option.dataset.kind = asset.kind;
|
||||
option.dataset.ownershipType = String(asset.ownershipType ?? -1);
|
||||
option.dataset.balance = String(asset.balance || "0");
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
|
@ -73,9 +79,10 @@ function populateSwapAssetSelect(select, assets, includeBalance) {
|
|||
}
|
||||
|
||||
function swapRegistryNftKeys(nfts) {
|
||||
return new Set(nfts.map((nft) => {
|
||||
return `${String(nft.nft_name || "").trim().toLowerCase()}|${Number(nft.series || 0)}`;
|
||||
}));
|
||||
return new Map(nfts.map((nft) => [
|
||||
nftRegistryKey(nft.nft_name, nft.series),
|
||||
Number(nft.ownership_type)
|
||||
]));
|
||||
}
|
||||
|
||||
function refreshSwapOfferSelects() {
|
||||
|
|
@ -84,11 +91,13 @@ function refreshSwapOfferSelects() {
|
|||
.map((balance) => {
|
||||
const asset = String(balance.asset || "").trim();
|
||||
const nftSeries = Number(balance.nft_series || 0);
|
||||
const isNft = swapNftKeys.has(`${asset.toLowerCase()}|${nftSeries}`);
|
||||
const ownershipType = swapNftMetadata.get(nftRegistryKey(asset, nftSeries));
|
||||
const isNft = ownershipType !== undefined;
|
||||
return {
|
||||
asset,
|
||||
nftSeries,
|
||||
kind: isNft ? "nft" : (isBaseTransferAsset(asset) ? "coin" : "token"),
|
||||
ownershipType: isNft ? ownershipType : -1,
|
||||
balance: String(balance.balance || "0")
|
||||
};
|
||||
});
|
||||
|
|
@ -125,7 +134,8 @@ async function loadSwapOfferAssets() {
|
|||
activeWalletBalances = Array.isArray(balances) ? balances : [];
|
||||
const tokenRows = Array.isArray(tokens) ? tokens : [];
|
||||
const nftRows = Array.isArray(nfts) ? nfts : [];
|
||||
swapNftKeys = swapRegistryNftKeys(nftRows);
|
||||
swapNftMetadata = swapRegistryNftKeys(nftRows);
|
||||
updateNftOwnershipRegistry(nftRows, assetsKey);
|
||||
|
||||
swapNetworkAssets = [
|
||||
{ asset: activeBaseCoin(), nftSeries: 0, kind: "coin", balance: "0" },
|
||||
|
|
@ -133,12 +143,14 @@ async function loadSwapOfferAssets() {
|
|||
asset: String(token.ticker || "").trim(),
|
||||
nftSeries: 0,
|
||||
kind: "token",
|
||||
ownershipType: -1,
|
||||
balance: "0"
|
||||
})),
|
||||
...nftRows.map((nft) => ({
|
||||
asset: String(nft.nft_name || "").trim(),
|
||||
nftSeries: Number(nft.series || 0),
|
||||
kind: "nft",
|
||||
ownershipType: Number(nft.ownership_type),
|
||||
balance: "0"
|
||||
}))
|
||||
];
|
||||
|
|
@ -164,7 +176,7 @@ function updateSwapSide(asset, amountInput, tipInput) {
|
|||
tipInput.value = "";
|
||||
return;
|
||||
}
|
||||
if (asset.kind === "nft") {
|
||||
if (asset.kind === "nft" && asset.ownershipType === 0) {
|
||||
amountInput.value = "1";
|
||||
amountInput.disabled = true;
|
||||
tipInput.value = "0";
|
||||
|
|
@ -172,7 +184,10 @@ function updateSwapSide(asset, amountInput, tipInput) {
|
|||
return;
|
||||
}
|
||||
amountInput.disabled = false;
|
||||
tipInput.disabled = false;
|
||||
tipInput.disabled = asset.kind === "nft";
|
||||
if (asset.kind === "nft") {
|
||||
tipInput.value = "0";
|
||||
}
|
||||
const amount = decimalToAtomic(amountInput.value);
|
||||
if (!amount || amount <= 0n) {
|
||||
if (!tipInput.value) {
|
||||
|
|
@ -198,7 +213,9 @@ function updateSwapOfferDerivedFields() {
|
|||
if (swapAvailable) {
|
||||
swapAvailable.textContent = offered
|
||||
? (offered.kind === "nft"
|
||||
? "1 NFT"
|
||||
? (offered.ownershipType === 1
|
||||
? `${formatAtomicBalance(String(offered.balance))} available`
|
||||
: "1 NFT")
|
||||
: `${formatAtomicBalance(String(offered.balance))} ${offered.asset}`)
|
||||
: "--";
|
||||
}
|
||||
|
|
@ -211,10 +228,13 @@ function validateSwapSide(asset, amountInput, tipInput, label) {
|
|||
if (!asset) {
|
||||
return `Select the asset ${label}.`;
|
||||
}
|
||||
const amount = asset.kind === "nft" ? 1n : decimalToAtomic(amountInput.value);
|
||||
const amount = decimalToAtomic(amountInput.value);
|
||||
if (amount === null || amount <= 0n) {
|
||||
return `Enter a valid amount ${label}.`;
|
||||
}
|
||||
if (asset.kind === "nft" && asset.ownershipType === 0 && amount !== ATOMIC_UNITS_PER_COIN) {
|
||||
return `Indivisible NFT amounts must be exactly 1 ${label}.`;
|
||||
}
|
||||
const tip = decimalToAtomic(tipInput.value || "0");
|
||||
if (tip === null) {
|
||||
return `Enter a valid miner tip ${label}.`;
|
||||
|
|
@ -260,7 +280,7 @@ function validateSwapOfferForm() {
|
|||
return "Offer expiration must be between 1 and 720 hours.";
|
||||
}
|
||||
|
||||
const amount = offered.kind === "nft" ? 1n : decimalToAtomic(swapOfferAmountInput.value);
|
||||
const amount = decimalToAtomic(swapOfferAmountInput.value);
|
||||
const tip = offered.kind === "nft" ? 0n : decimalToAtomic(swapOfferTipInput.value);
|
||||
if (amount + tip > offered.balance) {
|
||||
return "Offered amount plus miner tip exceeds the available asset balance.";
|
||||
|
|
@ -279,8 +299,8 @@ function validateSwapOfferForm() {
|
|||
function swapOfferPayload() {
|
||||
const offered = selectedSwapAsset(swapOfferAssetSelect);
|
||||
const requested = selectedSwapAsset(swapRequestAssetSelect);
|
||||
const value1 = offered.kind === "nft" ? 1n : decimalToAtomic(swapOfferAmountInput.value);
|
||||
const value2 = requested.kind === "nft" ? 1n : decimalToAtomic(swapRequestAmountInput.value);
|
||||
const value1 = decimalToAtomic(swapOfferAmountInput.value);
|
||||
const value2 = decimalToAtomic(swapRequestAmountInput.value);
|
||||
const tip1 = offered.kind === "nft" ? 0n : decimalToAtomic(swapOfferTipInput.value);
|
||||
const tip2 = requested.kind === "nft" ? 0n : decimalToAtomic(swapRequestTipInput.value);
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -357,12 +357,12 @@ function parseDashboardTransactionRows(
|
|||
return [dashboardRow(base, "Fee Earned", activeBaseCoin(), addressLabel(creator), signedAmount("+", fee))];
|
||||
}
|
||||
|
||||
if (type === 4) {
|
||||
const creator = bytesToAddressHex(bytes.slice(5, 27));
|
||||
const series = Number(bytes[27] || 0);
|
||||
const nftName = bytesToText(bytes.slice(28, 43)) || "NFT";
|
||||
const count = readU32LE(bytes, 143);
|
||||
const fee = readU64LE(bytes, 247);
|
||||
if (type === 4) {
|
||||
const creator = bytesToAddressHex(bytes.slice(5, 27));
|
||||
const series = Number(bytes[27] || 0);
|
||||
const nftName = bytesToText(bytes.slice(29, 44)) || "NFT";
|
||||
const count = readU32LE(bytes, 144);
|
||||
const fee = readU64LE(bytes, 248);
|
||||
if (isActiveAddress(creator)) {
|
||||
return [dashboardRow(base, "NFT Created", assetWithSeries(nftName, series), "Self", `+${count}`, signedBaseFee("-", fee))];
|
||||
}
|
||||
|
|
@ -556,11 +556,12 @@ function decodeLightweightTransaction(bytesLike) {
|
|||
time: time(),
|
||||
creator: bytesToAddressHex(bytes.slice(5, 27)),
|
||||
series: Number(bytes[27] || 0),
|
||||
nft_name: bytesToText(bytes.slice(28, 43)),
|
||||
item_ipfs: bytesToText(bytes.slice(43, 143)),
|
||||
count: readU32LE(bytes, 143),
|
||||
description: bytesToText(bytes.slice(147, 247)),
|
||||
txfee: Number(readU64LE(bytes, 247))
|
||||
ownership_type: Number(bytes[28] || 0),
|
||||
nft_name: bytesToText(bytes.slice(29, 44)),
|
||||
item_ipfs: bytesToText(bytes.slice(44, 144)),
|
||||
count: readU32LE(bytes, 144),
|
||||
description: bytesToText(bytes.slice(148, 248)),
|
||||
txfee: Number(readU64LE(bytes, 248))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,40 @@ const transferBroadcastButton = document.querySelector("[data-transfer-broadcast
|
|||
const ATOMIC_UNITS_PER_COIN = 100000000n;
|
||||
const NON_BASE_TRANSFER_MIN_FEE_ATOMIC = 100000000n;
|
||||
let transferBalancesRefreshPromise = null;
|
||||
let nftOwnershipRegistry = new Map();
|
||||
let nftOwnershipRegistryKey = "";
|
||||
|
||||
function nftRegistryKey(asset, series) {
|
||||
return `${String(asset || "").trim().toLowerCase()}|${Number(series || 0)}`;
|
||||
}
|
||||
|
||||
function updateNftOwnershipRegistry(nfts, registryKey = "") {
|
||||
nftOwnershipRegistry = new Map(
|
||||
(Array.isArray(nfts) ? nfts : []).map((nft) => [
|
||||
nftRegistryKey(nft.nft_name, nft.series),
|
||||
{
|
||||
ownershipType: Number(nft.ownership_type),
|
||||
remainingSupply: BigInt(nft.remaining_supply || "0")
|
||||
}
|
||||
])
|
||||
);
|
||||
nftOwnershipRegistryKey = registryKey;
|
||||
}
|
||||
|
||||
function nftOwnershipMetadata(asset, series) {
|
||||
return nftOwnershipRegistry.get(nftRegistryKey(asset, series)) || null;
|
||||
}
|
||||
|
||||
async function ensureNftOwnershipRegistry() {
|
||||
const invoke = window.__TAURI__?.core?.invoke;
|
||||
const broadcastNode = activeBroadcastNode();
|
||||
const registryKey = `${activeWalletAddressShort || activeWalletAddressHex}|${broadcastNode}`;
|
||||
if (!invoke || !broadcastNode || nftOwnershipRegistryKey === registryKey) {
|
||||
return;
|
||||
}
|
||||
const nfts = await invoke("nft_list", { broadcastNode });
|
||||
updateNftOwnershipRegistry(nfts, registryKey);
|
||||
}
|
||||
|
||||
function setTransferMessage(message, type = "") {
|
||||
setScopedTransactionMessage("Transfer", transferMessage, message, type);
|
||||
|
|
@ -79,6 +113,8 @@ function selectedTransferAsset() {
|
|||
return {
|
||||
asset: option.dataset.asset || "",
|
||||
nftSeries: Number(option.dataset.nftSeries || 0),
|
||||
ownershipType: Number(option.dataset.ownershipType || -1),
|
||||
isNft: option.dataset.isNft === "true",
|
||||
balance: BigInt(option.dataset.balance || "0")
|
||||
};
|
||||
}
|
||||
|
|
@ -122,7 +158,7 @@ function updateTransferDerivedFields() {
|
|||
}
|
||||
|
||||
if (transferAmountInput) {
|
||||
if (selected.nftSeries > 0) {
|
||||
if (selected.isNft && selected.ownershipType === 0) {
|
||||
transferAmountInput.value = "1";
|
||||
transferAmountInput.disabled = true;
|
||||
} else {
|
||||
|
|
@ -144,7 +180,7 @@ function updateTransferFee() {
|
|||
return;
|
||||
}
|
||||
|
||||
const amount = selected.nftSeries > 0 ? 1n : decimalToAtomic(transferAmountInput?.value);
|
||||
const amount = decimalToAtomic(transferAmountInput?.value);
|
||||
if (amount === null || amount <= 0n) {
|
||||
transferFeeInput.value = "";
|
||||
return;
|
||||
|
|
@ -188,6 +224,9 @@ function refreshTransferAssetOptions() {
|
|||
option.dataset.asset = balance.asset || "";
|
||||
option.dataset.nftSeries = String(Number(balance.nft_series || 0));
|
||||
option.dataset.balance = String(balance.balance || "0");
|
||||
const metadata = nftOwnershipMetadata(balance.asset, balance.nft_series);
|
||||
option.dataset.isNft = String(Boolean(metadata));
|
||||
option.dataset.ownershipType = String(metadata?.ownershipType ?? -1);
|
||||
transferAssetSelect.appendChild(option);
|
||||
});
|
||||
|
||||
|
|
@ -199,7 +238,7 @@ function refreshTransferAssetOptions() {
|
|||
}
|
||||
|
||||
async function refreshTransferBalancesIfNeeded() {
|
||||
if (activeWalletBalances.length || !walletLoaded || !registrationRegistered) {
|
||||
if (!walletLoaded || !registrationRegistered) {
|
||||
refreshTransferAssetOptions();
|
||||
return;
|
||||
}
|
||||
|
|
@ -218,7 +257,12 @@ async function refreshTransferBalancesIfNeeded() {
|
|||
transferBalancesRefreshPromise = (async () => {
|
||||
setTransferMessage("Loading spendable assets...");
|
||||
try {
|
||||
const balances = await invoke("get_active_wallet_balances", { broadcastNode });
|
||||
const [balances] = await Promise.all([
|
||||
activeWalletBalances.length
|
||||
? Promise.resolve(activeWalletBalances)
|
||||
: invoke("get_active_wallet_balances", { broadcastNode }),
|
||||
ensureNftOwnershipRegistry()
|
||||
]);
|
||||
activeWalletBalances = Array.isArray(balances) ? balances : [];
|
||||
refreshTransferAssetOptions();
|
||||
setTransferMessage("");
|
||||
|
|
@ -326,11 +370,15 @@ function validateTransferForm() {
|
|||
return "Receiver cannot be the active wallet address.";
|
||||
}
|
||||
|
||||
const amount = selected.nftSeries > 0 ? 1n : decimalToAtomic(transferAmountInput?.value);
|
||||
const amount = decimalToAtomic(transferAmountInput?.value);
|
||||
if (amount === null || amount <= 0n) {
|
||||
return "Enter a valid amount with up to 8 decimal places.";
|
||||
}
|
||||
if (selected.nftSeries > 0 && String(transferAmountInput?.value || "").trim() !== "1") {
|
||||
if (
|
||||
selected.isNft
|
||||
&& selected.ownershipType === 0
|
||||
&& amount !== ATOMIC_UNITS_PER_COIN
|
||||
) {
|
||||
return "NFT transfers must send exactly 1 item.";
|
||||
}
|
||||
if (amount > selected.balance) {
|
||||
|
|
@ -360,7 +408,7 @@ function validateTransferForm() {
|
|||
|
||||
function transferPayload() {
|
||||
const selected = selectedTransferAsset();
|
||||
const value = selected?.nftSeries > 0 ? 1n : decimalToAtomic(transferAmountInput?.value);
|
||||
const value = decimalToAtomic(transferAmountInput?.value);
|
||||
const fee = decimalToAtomic(transferFeeInput?.value || "0");
|
||||
|
||||
return {
|
||||
|
|
@ -420,7 +468,7 @@ async function handleTransferAction(action) {
|
|||
if (!broadcastNode) {
|
||||
throw new Error("Select a broadcast node before saving.");
|
||||
}
|
||||
setTransferMessage("Saving transfer...");
|
||||
setTransferMessage("Signing and saving transfer...");
|
||||
const review = await invoke("save_transfer_transaction", {
|
||||
...transferPayload(),
|
||||
broadcastNode,
|
||||
|
|
@ -436,7 +484,7 @@ async function handleTransferAction(action) {
|
|||
if (!broadcastNode) {
|
||||
throw new Error("Select a broadcast node before broadcasting.");
|
||||
}
|
||||
setTransferMessage("Broadcasting transfer...");
|
||||
setTransferMessage("Signing and broadcasting transfer...");
|
||||
const result = await invoke("broadcast_transfer_transaction", {
|
||||
...transferPayload(),
|
||||
broadcastNode,
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ async function handleVanityAction(action) {
|
|||
setVanityButtonsDisabled(true);
|
||||
try {
|
||||
if (action === "save") {
|
||||
setVanityMessage("Saving vanity address transaction...");
|
||||
setVanityMessage("Signing and saving vanity address transaction...");
|
||||
const review = await invoke("save_vanity_address_transaction", {
|
||||
...vanityPayload(),
|
||||
walletKey
|
||||
|
|
@ -196,7 +196,7 @@ async function handleVanityAction(action) {
|
|||
if (!broadcastNode) {
|
||||
throw new Error("Select a broadcast node before broadcasting.");
|
||||
}
|
||||
setVanityMessage("Broadcasting vanity address transaction...");
|
||||
setVanityMessage("Signing and broadcasting vanity address transaction...");
|
||||
const result = await invoke("broadcast_vanity_address_transaction", {
|
||||
...vanityPayload(),
|
||||
broadcastNode,
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ async fn nft_list(
|
|||
if let Some(error) = parse_error_response(&response) {
|
||||
return Err(error);
|
||||
}
|
||||
const NFT_LIST_ROW_BYTES: usize = 32 + 15 + 4;
|
||||
const NFT_LIST_ROW_BYTES: usize = 32 + 15 + 4 + 1 + 8;
|
||||
if response.len() % NFT_LIST_ROW_BYTES != 0 {
|
||||
return Err("NFT list response had an unexpected byte length.".to_string());
|
||||
}
|
||||
|
|
@ -488,6 +488,12 @@ async fn nft_list(
|
|||
.try_into()
|
||||
.map_err(|_| "Failed to parse NFT series.".to_string())?,
|
||||
);
|
||||
let ownership_type = chunk[51];
|
||||
let remaining_supply = u64::from_le_bytes(
|
||||
chunk[52..60]
|
||||
.try_into()
|
||||
.map_err(|_| "Failed to parse NFT remaining supply.".to_string())?,
|
||||
);
|
||||
if nft_name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -495,6 +501,8 @@ async fn nft_list(
|
|||
nft_name,
|
||||
series,
|
||||
origin_txid,
|
||||
ownership_type,
|
||||
remaining_supply: remaining_supply.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1152,15 +1160,11 @@ async fn build_transfer_json(
|
|||
}
|
||||
|
||||
let coin = padded_coin_field(&coin)?;
|
||||
let mut value = parse_atomic_u64(&value, "Transfer value")?;
|
||||
let value = parse_atomic_u64(&value, "Transfer value")?;
|
||||
let txfee = parse_atomic_u64(&txfee, "Transfer fee")?;
|
||||
if value == 0 {
|
||||
return Err("Transfer value must be greater than zero.".to_string());
|
||||
}
|
||||
if nft_series > 0 {
|
||||
value = 1;
|
||||
}
|
||||
|
||||
let is_base_coin = coin.trim().eq_ignore_ascii_case(BASECOIN.trim());
|
||||
let minimum_fee = minimum_transfer_fee(value, is_base_coin);
|
||||
if txfee < minimum_fee {
|
||||
|
|
@ -1247,6 +1251,7 @@ async fn build_create_nft_json(
|
|||
state: &State<'_, WalletState>,
|
||||
wallet_key: Option<String>,
|
||||
series: u8,
|
||||
ownership_type: u8,
|
||||
nft_name: String,
|
||||
item_ipfs: String,
|
||||
count: u32,
|
||||
|
|
@ -1263,6 +1268,12 @@ async fn build_create_nft_json(
|
|||
if series == 1 && count < 2 {
|
||||
return Err("An NFT collection must contain at least 2 items.".to_string());
|
||||
}
|
||||
if ownership_type > 1 {
|
||||
return Err("Ownership type must be indivisible or fractional.".to_string());
|
||||
}
|
||||
if series == 1 && ownership_type != 0 {
|
||||
return Err("NFT/RWA collections must use indivisible ownership.".to_string());
|
||||
}
|
||||
|
||||
let nft_name = padded_token_ticker(&nft_name)
|
||||
.map_err(|_| "NFT name must normalize to 3-15 alphanumeric characters.".to_string())?;
|
||||
|
|
@ -1290,6 +1301,7 @@ async fn build_create_nft_json(
|
|||
timestamp,
|
||||
&creator,
|
||||
series,
|
||||
ownership_type,
|
||||
&nft_name,
|
||||
&item_ipfs,
|
||||
count,
|
||||
|
|
@ -1307,6 +1319,7 @@ async fn build_create_nft_json(
|
|||
"timestamp": timestamp,
|
||||
"creator": creator,
|
||||
"series": series,
|
||||
"ownership_type": ownership_type,
|
||||
"nft_name": nft_name,
|
||||
"item_ipfs": item_ipfs,
|
||||
"count": count,
|
||||
|
|
@ -2732,6 +2745,7 @@ async fn rebuild_create_nft(
|
|||
json_u32(transaction, "timestamp", "time")?,
|
||||
&json_str(transaction, "creator")?,
|
||||
json_u8(transaction, "series")?,
|
||||
json_u8(transaction, "ownership_type")?,
|
||||
&json_str(transaction, "nft_name")?,
|
||||
&json_str(transaction, "item_ipfs")?,
|
||||
json_u64(transaction, "count")? as u32,
|
||||
|
|
@ -3990,6 +4004,7 @@ async fn save_create_nft_transaction(
|
|||
state: State<'_, WalletState>,
|
||||
wallet_key: Option<String>,
|
||||
series: u8,
|
||||
ownership_type: u8,
|
||||
nft_name: String,
|
||||
item_ipfs: String,
|
||||
count: u32,
|
||||
|
|
@ -4001,6 +4016,7 @@ async fn save_create_nft_transaction(
|
|||
&state,
|
||||
wallet_key,
|
||||
series,
|
||||
ownership_type,
|
||||
nft_name,
|
||||
item_ipfs,
|
||||
count,
|
||||
|
|
@ -4017,6 +4033,7 @@ async fn broadcast_create_nft_transaction(
|
|||
wallet_key: Option<String>,
|
||||
broadcast_node: String,
|
||||
series: u8,
|
||||
ownership_type: u8,
|
||||
nft_name: String,
|
||||
item_ipfs: String,
|
||||
count: u32,
|
||||
|
|
@ -4027,6 +4044,7 @@ async fn broadcast_create_nft_transaction(
|
|||
&state,
|
||||
wallet_key,
|
||||
series,
|
||||
ownership_type,
|
||||
nft_name,
|
||||
item_ipfs,
|
||||
count,
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ struct NftListItemView {
|
|||
nft_name: String,
|
||||
series: u32,
|
||||
origin_txid: String,
|
||||
ownership_type: u8,
|
||||
remaining_supply: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue