Skip to content

Commit

Permalink
Improved UTXO consolidation. Update wallet immediately after transact…
Browse files Browse the repository at this point in the history
…ion without waiting for sync.
  • Loading branch information
photonic committed Oct 10, 2024
1 parent daaa6ac commit 8fc58f6
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 80 deletions.
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@photonic/app",
"private": true,
"version": "1.0.6",
"version": "1.0.7",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/components/ConsolidationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,6 @@ const OutputCounts = () => {
(
await db.subscriptionStatus.toArray()
).map(async (sub) => {
db.txo.where({ ContractType });

const count = await db.txo
.where({ contractType: sub.contractType, spent: 0 })
.count();
Expand All @@ -188,7 +186,8 @@ const OutputCounts = () => {
([contractType, count]) =>
contractType !== ContractType.NFT && (
<div key={contractType}>
<ContractName contractType={contractType} /> - {count}
<ContractName contractType={contractType} />: {count}{" "}
{count === 1 ? "UTXO" : "UTXOs"}
</div>
)
);
Expand Down Expand Up @@ -218,6 +217,7 @@ export default function ConsolidationModal() {
status: "success",
});
} catch (error) {
setWaiting(false);
if (error instanceof Error) {
toast({
title: error.message,
Expand Down
44 changes: 36 additions & 8 deletions packages/app/src/components/SendDigitalObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,30 @@ import {
Alert,
AlertDescription,
AlertIcon,
useToast,
} from "@chakra-ui/react";
import { photonsToRXD } from "@lib/format";
import { useLiveQuery } from "dexie-react-hooks";
import db from "@app/db";
import { ContractType, TxO } from "@app/types";
import { ContractType, SmartToken, TxO } from "@app/types";
import { p2pkhScript, nftScript } from "@lib/script";
import { buildTx } from "@lib/tx";
import Identifier from "./Identifier";
import Outpoint from "@lib/Outpoint";
import { feeRate, network, wallet } from "@app/signals";
import { electrumWorker } from "@app/electrum/Electrum";
import { updateRxdBalances, updateWalletUtxos } from "@app/utxos";

interface Props {
asset: TxO;
glyph: SmartToken;
txo: TxO;
onSuccess?: (txid: string) => void;
disclosure: UseDisclosureProps;
}

export default function SendDigitalObject({
asset,
glyph,
txo,
onSuccess,
disclosure,
}: Props) {
Expand All @@ -46,7 +50,8 @@ export default function SendDigitalObject({
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(true);
const [errorMessage, setErrorMessage] = useState("");
const ref = Outpoint.fromString(asset.script.substring(2, 74));
const toast = useToast();
const ref = Outpoint.fromString(txo.script.substring(2, 74));

const rxd = useLiveQuery(
() => db.txo.where({ contractType: ContractType.RXD, spent: 0 }).toArray(),
Expand Down Expand Up @@ -78,7 +83,7 @@ export default function SendDigitalObject({
}

// Set script to "" so P2PKH scriptSig is used for fee calculation
const required: SelectableInput = { ...asset, required: true, script: "" };
const required: SelectableInput = { ...txo, required: true, script: "" };
const inputs: SelectableInput[] = [required, ...rxd.slice()];

const changeScript = p2pkhScript(wallet.value.address);
Expand All @@ -90,7 +95,7 @@ export default function SendDigitalObject({
const selected = coinSelect(
wallet.value.address,
inputs,
[{ script, value: asset.value }],
[{ script, value: txo.value }],
changeScript,
feeRate.value
);
Expand All @@ -101,7 +106,7 @@ export default function SendDigitalObject({
return;
}

selected.inputs[0].script = asset.script;
selected.inputs[0].script = txo.script;

const privKey = PrivateKey.fromString(wallet.value.wif as string);

Expand All @@ -115,9 +120,32 @@ export default function SendDigitalObject({
try {
const txid = await electrumWorker.value.broadcast(rawTx);
db.broadcast.put({ txid, date: Date.now(), description: "nft_send" });

toast({
title: t`Sent NFT`,
status: "success",
});

updateWalletUtxos(
ContractType.NFT,
txo.script, // NFT script, if sent to self
changeScript, // RXD change
txid,
selected.inputs,
selected.outputs
).then((newTxoIds) => {
// If sent to self, update lastTxoId of glyph
if (newTxoIds.length && glyph.id) {
db.glyph.update(glyph.id, { lastTxoId: newTxoIds.pop() });
}
// Update RXD change
updateRxdBalances(wallet.value.address);
});

onSuccess && onSuccess(txid);
} catch (error) {
setErrorMessage(t`Transaction rejected`);
console.debug(error);
setSuccess(false);
setLoading(false);
}
Expand Down Expand Up @@ -158,7 +186,7 @@ export default function SendDigitalObject({
</FormControl>
<FormControl>
<FormLabel>{t`Amount`}</FormLabel>
<Identifier>{`${photonsToRXD(asset.value)} ${
<Identifier>{`${photonsToRXD(txo.value)} ${
network.value.ticker
}`}</Identifier>
</FormControl>
Expand Down
11 changes: 11 additions & 0 deletions packages/app/src/components/SendFungible.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import TokenContent from "./TokenContent";
import { RiQuestionFill } from "react-icons/ri";
import { electrumWorker } from "@app/electrum/Electrum";
import FtBalance from "./FtBalance";
import { updateFtBalances, updateWalletUtxos } from "@app/utxos";

interface Props {
glyph: SmartToken;
Expand Down Expand Up @@ -108,6 +109,7 @@ export default function SendFungible({ glyph, onSuccess, disclosure }: Props) {

const outputs = [{ script: toScript, value }];
if (accum.sum > value) {
// Create FT change output
outputs.push({ script: fromScript, value: accum.sum - value });
}

Expand Down Expand Up @@ -142,6 +144,15 @@ export default function SendFungible({ glyph, onSuccess, disclosure }: Props) {
status: "success",
});

updateWalletUtxos(
ContractType.FT,
fromScript, // FT change
changeScript, // RXD change
txid,
selected.inputs,
selected.outputs
).then(() => updateFtBalances(new Set([fromScript])));

onSuccess && onSuccess(txid);
} catch (error) {
setErrorMessage(t`Could not send transaction`);
Expand Down
13 changes: 12 additions & 1 deletion packages/app/src/components/SendRXD.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { t } from "@lingui/macro";
import { PrivateKey } from "@radiantblockchain/radiantjs";
import Big from "big.js";
import coinSelect, { SelectableInput } from "@lib/coinSelect";
import coinSelect, { SelectableInput, updateUnspent } from "@lib/coinSelect";
import {
Modal,
ModalOverlay,
Expand Down Expand Up @@ -35,6 +35,7 @@ import { buildTx } from "@lib/tx";
import { feeRate, network, wallet } from "@app/signals";
import { electrumWorker } from "@app/electrum/Electrum";
import Balance from "./Balance";
import { updateRxdBalances, updateWalletUtxos } from "@app/utxos";

interface Props {
onSuccess?: (txid: string) => void;
Expand Down Expand Up @@ -132,6 +133,16 @@ export default function SendRXD({ onSuccess, disclosure }: Props) {
status: "success",
});

// Update UTXOs without waiting for subscription
updateWalletUtxos(
ContractType.RXD,
changeScript,
changeScript,
txid,
selected.inputs,
selected.outputs
).then(() => updateRxdBalances(wallet.value.address));

onSuccess && onSuccess(txid);
} catch (error) {
setErrorMessage(t`Could not send transaction`);
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/components/ViewDigitalObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,8 @@ export default function ViewDigitalObject({
</Container>
</Grid>
<SendDigitalObject
asset={txo}
glyph={nft}
txo={txo}
disclosure={sendDisclosure}
onSuccess={(txid) => {
sendDisclosure.onClose();
Expand Down
52 changes: 5 additions & 47 deletions packages/app/src/electrum/worker/FT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import db from "@app/db";
import Outpoint, { reverseRef } from "@lib/Outpoint";
import setSubscriptionStatus from "./setSubscriptionStatus";
import { Worker } from "./electrumWorker";
import { consolidationCheck } from "./consolidationCheck";
import { updateFtBalances } from "@app/utxos";

export class FTWorker extends NFTWorker {
protected ready = true;
Expand Down Expand Up @@ -47,10 +49,7 @@ export class FTWorker extends NFTWorker {
this.ready = false;
this.lastReceivedStatus = status;

const { added, spent, utxoCount } = await this.updateTXOs(
scriptHash,
status
);
const { added, spent } = await this.updateTXOs(scriptHash, status);

// TODO there is some duplication in NFT and FT classes

Expand Down Expand Up @@ -96,24 +95,7 @@ export class FTWorker extends NFTWorker {
...spent.map(({ script }) => script),
]);

// Update balances
for (const script of touched) {
let confirmed = 0;
let unconfirmed = 0;
await db.txo.where({ script, spent: 0 }).each(({ height, value }) => {
if (height === Infinity) {
unconfirmed += value;
} else {
confirmed += value;
}
});
const { ref } = parseFtScript(script);
db.balance.put({
id: reverseRef(ref as string),
confirmed,
unconfirmed,
});
}
updateFtBalances(touched);

setSubscriptionStatus(scriptHash, status, ContractType.FT);
this.ready = true;
Expand All @@ -125,31 +107,7 @@ export class FTWorker extends NFTWorker {
}
}

// Check if consolidation is required
let consolidationRequired = false;
if (utxoCount && utxoCount > 20) {
(
await db.txo
.where({
contractType: ContractType.FT,
spent: 0,
})
.toArray()
).reduce((acc, cur) => {
if (!acc[cur.script]) {
acc[cur.script] = 0;
}
acc[cur.script] += 1;
if (acc[cur.script] > 10) {
consolidationRequired = true;
}
return acc;
}, {} as { [key: string]: number });
}

if (consolidationRequired) {
db.kvp.put(true, "consolidationRequired");
}
consolidationCheck();
}

async register(address: string) {
Expand Down
9 changes: 8 additions & 1 deletion packages/app/src/electrum/worker/NFT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import setSubscriptionStatus from "./setSubscriptionStatus";
import { batchRequests } from "@lib/util";
import { GLYPH_FT, GLYPH_NFT } from "@lib/protocols";
import { Worker } from "./electrumWorker";
import { consolidationCheck } from "./consolidationCheck";

// 500KB size limit
const fileSizeLimit = 500_000;
Expand Down Expand Up @@ -96,7 +97,11 @@ export class NFTWorker implements Subscription {
console.debug("Duplicate subscription received", status);
return;
}
if (!this.ready || !this.worker.active) {
if (
!this.ready ||
!this.worker.active ||
(await db.kvp.get("consolidationRequired"))
) {
this.receivedStatuses.push(status);
return;
}
Expand Down Expand Up @@ -168,6 +173,8 @@ export class NFTWorker implements Subscription {
this.onSubscriptionReceived(scriptHash, lastStatus);
}
}

consolidationCheck();
}

async register(address: string) {
Expand Down
22 changes: 5 additions & 17 deletions packages/app/src/electrum/worker/RXD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import db from "@app/db";
import ElectrumManager from "@app/electrum/ElectrumManager";
import setSubscriptionStatus from "./setSubscriptionStatus";
import { Worker } from "./electrumWorker";
import { consolidationCheck } from "./consolidationCheck";
import { updateRxdBalances } from "@app/utxos";

export class RXDWorker implements Subscription {
protected worker: Worker;
Expand Down Expand Up @@ -58,25 +60,13 @@ export class RXDWorker implements Subscription {
this.ready = false;
this.lastReceivedStatus = status;

const { added, utxoCount } = await this.updateTXOs(scriptHash, status);
const { added } = await this.updateTXOs(scriptHash, status);

added.map((txo) => db.txo.put(txo).catch());

// Update balances
let confirmed = 0;
let unconfirmed = 0;
await db.txo
.where({ contractType: ContractType.RXD, spent: 0 })
.each(({ height, value }) => {
if (height === Infinity) {
unconfirmed += value;
} else {
confirmed += value;
}
});
updateRxdBalances(this.address);

setSubscriptionStatus(scriptHash, status, ContractType.RXD);
db.balance.put({ id: this.address, confirmed, unconfirmed });
this.ready = true;
if (this.receivedStatuses.length > 0) {
const lastStatus = this.receivedStatuses.pop();
Expand All @@ -86,9 +76,7 @@ export class RXDWorker implements Subscription {
}
}

if (utxoCount && utxoCount > 20) {
db.kvp.put(true, "consolidationRequired");
}
consolidationCheck();
}

async register(address: string) {
Expand Down
Loading

0 comments on commit 8fc58f6

Please sign in to comment.