-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'box-consolidations' into 'dev'
Box consolidations See merge request ergo/minotaur/minotaur-wallet!31
- Loading branch information
Showing
23 changed files
with
656 additions
and
247 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { MigrationInterface, QueryRunner } from 'typeorm'; | ||
|
||
export class SpendBoxInfo1719388610915 implements MigrationInterface { | ||
name = 'SpendBoxInfo1719388610915'; | ||
public async up(queryRunner: QueryRunner): Promise<void> { | ||
const queries = [ | ||
'CREATE TABLE "temporary_wallet" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" text NOT NULL, "network_type" text NOT NULL, "seed" text NOT NULL, "extended_public_key" text NOT NULL, "type" text NOT NULL, "required_sign" integer, "version" integer NOT NULL DEFAULT (1), "flags" text NOT NULL DEFAULT (\'\'), "encrypted_mnemonic" text NOT NULL DEFAULT (\'\'))', | ||
'INSERT INTO "temporary_wallet"("id", "name", "network_type", "seed", "extended_public_key", "type", "required_sign", "version") SELECT "id", "name", "network_type", "seed", "extended_public_key", "type", "required_sign", "version" FROM "wallet"', | ||
'DROP TABLE "wallet"', | ||
'ALTER TABLE "temporary_wallet" RENAME TO "wallet"', | ||
]; | ||
for (const query of queries) { | ||
await queryRunner.query(query); | ||
} | ||
} | ||
|
||
public async down(queryRunner: QueryRunner): Promise<void> { | ||
const queries = [ | ||
'CREATE TABLE "temporary_wallet" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" text NOT NULL, "network_type" text NOT NULL, "seed" text NOT NULL, "extended_public_key" text NOT NULL, "type" text NOT NULL, "required_sign" integer, "version" integer NOT NULL DEFAULT (1))', | ||
'INSERT INTO "temporary_wallet"("id", "name", "network_type", "seed", "extended_public_key", "type", "required_sign", "version") SELECT "id", "name", "network_type", "seed", "extended_public_key", "type", "required_sign", "version" FROM "wallet"', | ||
'DROP TABLE "wallet"', | ||
'ALTER TABLE "temporary_wallet" RENAME TO "wallet"', | ||
]; | ||
for (const query of queries) { | ||
await queryRunner.query(query); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import { SpendBoxInfo1719388610915 } from './1719388610915-spend-box-info'; | ||
import { initialize1687534587363 } from './1687534587363-initialize'; | ||
|
||
const Migrations = [initialize1687534587363]; | ||
const Migrations = [initialize1687534587363, SpendBoxInfo1719388610915]; | ||
|
||
export default Migrations; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { | ||
getWalletTransactions, | ||
getWalletTransactionsTotal, | ||
WalletTransactionType, | ||
} from '@/action/transaction'; | ||
import { StateWallet } from '@/store/reducer/wallet'; | ||
import { useEffect, useState } from 'react'; | ||
|
||
const useWalletTransaction = (wallet: StateWallet, limit: number) => { | ||
const [transactions, setTransactions] = useState< | ||
Array<WalletTransactionType> | ||
>([]); | ||
const [loading, setLoading] = useState(false); | ||
const [total, setTotal] = useState(0); | ||
const [resetTransactions, setResetTransactions] = useState(false); | ||
const [heights, setHeights] = useState<string>(''); | ||
useEffect(() => { | ||
const currentHeights = JSON.stringify( | ||
wallet.addresses.map((item) => item.proceedHeight), | ||
); | ||
if (!loading && currentHeights !== heights) { | ||
setLoading(true); | ||
getWalletTransactionsTotal(wallet.id).then((newTotal) => { | ||
if (newTotal !== total) { | ||
setTotal(newTotal); | ||
setResetTransactions(true); | ||
} | ||
setHeights(currentHeights); | ||
setLoading(false); | ||
}); | ||
} | ||
}, [wallet.addresses, wallet.id, loading, heights, total]); | ||
useEffect(() => { | ||
if ( | ||
!loading && | ||
(transactions.length < Math.min(total, limit) || resetTransactions) | ||
) { | ||
setLoading(true); | ||
getWalletTransactions( | ||
wallet.id, | ||
transactions.length, | ||
limit - transactions.length, | ||
).then((newTransactions) => { | ||
setTransactions([...transactions, ...newTransactions]); | ||
setResetTransactions(false); | ||
setLoading(false); | ||
}); | ||
} | ||
}, [loading, transactions, total, limit, wallet.id, resetTransactions]); | ||
return { | ||
transactions, | ||
loading, | ||
total, | ||
}; | ||
}; | ||
|
||
export default useWalletTransaction; |
181 changes: 181 additions & 0 deletions
181
src/pages/wallet-page/dapps/apps/box-consolidation/BoxConsolidation.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import { | ||
Button, | ||
Divider, | ||
FormControl, | ||
InputLabel, | ||
MenuItem, | ||
Select, | ||
SelectChangeEvent, | ||
Typography, | ||
} from '@mui/material'; | ||
import { DAppPropsType, UnsignedGeneratedTx } from '@/types/dapps'; | ||
import React, { useState } from 'react'; | ||
import DisplayId from '@/components/display-id/DisplayId'; | ||
import useAddresses from './useAddresses'; | ||
import useAddressBoxes from './useAddressBoxes'; | ||
import UnspentBoxesCount from './UnspentBoxesCount'; | ||
import OldestBoxAge from './OldestBoxAge'; | ||
import getColor from './getColor'; | ||
import * as wasm from 'ergo-lib-wasm-browser'; | ||
import { CONSOLIDATE_FEE, IMPLEMENTOR, TRANSACTION_FEE } from './parameters'; | ||
import { boxesToArrayBox } from '@/utils/convert'; | ||
import LoadingPage from '@/components/loading-page/LoadingPage'; | ||
|
||
const BoxConsolidation = (props: DAppPropsType) => { | ||
const addresses = useAddresses(props); | ||
const [selectedAddress, setSelectedAddress] = useState(0); | ||
const state = useAddressBoxes(addresses.addresses, selectedAddress, props); | ||
const [generating, setGenerating] = useState(false); | ||
const handleChangeAddress = (event: SelectChangeEvent) => { | ||
const index = parseInt(event.target.value); | ||
if (!isNaN(index) && index >= 0 && index < addresses.addresses.length) { | ||
setSelectedAddress(index); | ||
} | ||
}; | ||
const boxCountConsolidate = state.boxesCount >= 100; | ||
const ageConsolidate = state.oldestAge >= 3.0; | ||
const consolidate = boxCountConsolidate || ageConsolidate; | ||
const generateTransaction = () => { | ||
if (!generating) { | ||
setGenerating(true); | ||
props | ||
.getCoveringForErgAndToken( | ||
999999999000000000n, | ||
[], | ||
addresses.addresses[selectedAddress], | ||
) | ||
.then((covering) => { | ||
const height = state.height; | ||
const boxArray = boxesToArrayBox(covering.boxes); | ||
const totalErg = boxArray | ||
.map((item) => BigInt(item.value().as_i64().to_str())) | ||
.reduce((a, b) => a + b, 0n); | ||
const outputCandidates = wasm.ErgoBoxCandidates.empty(); | ||
const consolidated = new wasm.ErgoBoxCandidateBuilder( | ||
wasm.BoxValue.from_i64( | ||
wasm.I64.from_str( | ||
(totalErg - TRANSACTION_FEE - CONSOLIDATE_FEE).toString(), | ||
), | ||
), | ||
wasm.Contract.pay_to_address( | ||
wasm.Address.from_base58(addresses.addresses[selectedAddress]), | ||
), | ||
height, | ||
); | ||
const tokensAmount = new Map<string, bigint>(); | ||
boxArray.forEach((box) => { | ||
const tokens = box.tokens(); | ||
for (let index = 0; index < tokens.len(); index++) { | ||
const token = tokens.get(index); | ||
const tokenId = token.id().to_str(); | ||
const amount = BigInt(token.amount().as_i64().to_str()); | ||
tokensAmount.set( | ||
tokenId, | ||
amount + (tokensAmount.get(tokenId) ?? 0n), | ||
); | ||
} | ||
}); | ||
tokensAmount.forEach((value, key) => { | ||
consolidated.add_token( | ||
wasm.TokenId.from_str(key), | ||
wasm.TokenAmount.from_i64(wasm.I64.from_str(value.toString())), | ||
); | ||
}); | ||
outputCandidates.add(consolidated.build()); | ||
if (CONSOLIDATE_FEE > 0n) { | ||
const feeBox = new wasm.ErgoBoxCandidateBuilder( | ||
wasm.BoxValue.from_i64( | ||
wasm.I64.from_str(CONSOLIDATE_FEE.toString()), | ||
), | ||
wasm.Contract.pay_to_address( | ||
wasm.Address.from_base58(IMPLEMENTOR), | ||
), | ||
height, | ||
).build(); | ||
outputCandidates.add(feeBox); | ||
} | ||
const tx = wasm.TxBuilder.new( | ||
new wasm.BoxSelection( | ||
covering.boxes, | ||
new wasm.ErgoBoxAssetsDataList(), | ||
), | ||
outputCandidates, | ||
height, | ||
wasm.BoxValue.from_i64( | ||
wasm.I64.from_str(TRANSACTION_FEE.toString()), | ||
), | ||
wasm.Address.from_base58(addresses.addresses[selectedAddress]), | ||
).build(); | ||
const reduced = wasm.ReducedTransaction.from_unsigned_tx( | ||
tx, | ||
covering.boxes, | ||
wasm.ErgoBoxes.empty(), | ||
props.chain.fakeContext(), | ||
); | ||
const request: UnsignedGeneratedTx = { | ||
boxes: covering.boxes, | ||
tx: reduced, | ||
}; | ||
props.signAndSendTx(request); | ||
setGenerating(false); | ||
}); | ||
} | ||
}; | ||
return ( | ||
<React.Fragment> | ||
<FormControl sx={{ mb: 2 }}> | ||
<InputLabel>Address</InputLabel> | ||
<Select | ||
value={selectedAddress.toString()} | ||
onChange={handleChangeAddress} | ||
> | ||
{addresses.addresses.map((item, index) => ( | ||
<MenuItem key={item} value={index.toString()}> | ||
<DisplayId id={item} /> | ||
</MenuItem> | ||
))} | ||
</Select> | ||
</FormControl> | ||
{addresses.loading || state.loading ? ( | ||
<LoadingPage title="Loading address boxes" description="Please wait" /> | ||
) : state.boxesCount === 0 ? ( | ||
<Typography>There is no boxes in this address</Typography> | ||
) : ( | ||
<React.Fragment> | ||
<UnspentBoxesCount | ||
consolidate={boxCountConsolidate} | ||
boxCount={state.boxesCount} | ||
/> | ||
<OldestBoxAge age={state.oldestAge} consolidate={ageConsolidate} /> | ||
<Divider /> | ||
|
||
<Typography | ||
textAlign="center" | ||
fontSize="x-large" | ||
color={getColor(consolidate)} | ||
mt={3} | ||
mb={2} | ||
> | ||
{consolidate ? 'Consider consolidation' : 'You are fine!'} | ||
</Typography> | ||
<Typography | ||
textAlign="center" | ||
variant="body2" | ||
color="textSecondary" | ||
mb={3} | ||
> | ||
You can renew {state.boxesCount} box | ||
{state.boxesCount > 1 ? 'es' : ''}{' '} | ||
{consolidate ? '' : ', but it is not necessary.'} | ||
</Typography> | ||
<Button onClick={generateTransaction}> | ||
{/* {generating ? <Loading/> : undefined} */} | ||
Renew {state.boxesCount} box{state.boxesCount > 1 ? 'es' : ''} | ||
</Button> | ||
</React.Fragment> | ||
)} | ||
</React.Fragment> | ||
); | ||
}; | ||
|
||
export default BoxConsolidation; |
24 changes: 24 additions & 0 deletions
24
src/pages/wallet-page/dapps/apps/box-consolidation/ConsolidateIndicator.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Typography } from '@mui/material'; | ||
import React from 'react'; | ||
import getColor from './getColor'; | ||
import { CheckCircleOutline, WarningAmberRounded } from '@mui/icons-material'; | ||
|
||
interface ConsolidateIndicatorPropsType { | ||
consolidate: boolean; | ||
} | ||
const ConsolidateIndicator = (props: ConsolidateIndicatorPropsType) => { | ||
return ( | ||
<React.Fragment> | ||
<Typography | ||
color={getColor(props.consolidate)} | ||
fontWeight={500} | ||
sx={{ display: 'flex', alignItems: 'center', gap: 1 }} | ||
> | ||
{props.consolidate ? <WarningAmberRounded /> : <CheckCircleOutline />} | ||
{props.consolidate ? 'Consider consolidation' : "It's fine"} | ||
</Typography> | ||
</React.Fragment> | ||
); | ||
}; | ||
|
||
export default ConsolidateIndicator; |
30 changes: 30 additions & 0 deletions
30
src/pages/wallet-page/dapps/apps/box-consolidation/OldestBoxAge.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Typography } from '@mui/material'; | ||
import React from 'react'; | ||
import ConsolidateIndicator from './ConsolidateIndicator'; | ||
|
||
interface OldestBoxAgePropsType { | ||
age: number; | ||
consolidate: boolean; | ||
} | ||
|
||
const OldestBoxAge = (props: OldestBoxAgePropsType) => { | ||
return ( | ||
<React.Fragment> | ||
<Typography variant="body2" color="textSecondary"> | ||
Age of oldest box | ||
</Typography> | ||
<Typography mb={1}> | ||
<Typography component="span" fontSize="large"> | ||
{props.age.toFixed(2)} | ||
</Typography>{' '} | ||
years | ||
</Typography> | ||
<ConsolidateIndicator consolidate={props.consolidate} /> | ||
<Typography variant="body2" color="textSecondary" pl={4} mb={3}> | ||
Storage rent will be applied on boxes older than 4 years. | ||
</Typography> | ||
</React.Fragment> | ||
); | ||
}; | ||
|
||
export default OldestBoxAge; |
Oops, something went wrong.