-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UTXO Consolidation Helpers #3645
Comments
This is a great sketch. As mentioned, enabling this behavior by default can be tricky since it could result in multiple transactions being submitted upfront. Providing this functionality through additional parameters is a good approach, as it allows users to control when and how consolidation occurs. |
@nedsalk Let's focus on the helper methods first - did you think about a simple API to get started? const account = new Account(ADDRESS, provider);
account.consolidateCoins()
account.splitCoin() |
@arboleya below is the full consolidation workflow for both base and non-base assets. The complex solution aims to be smart and take into consideration the "optimal" base+non-base asset combinations. The simple solution consolidates all base assets into one before consolidating any non-base assets. UTXO consolidation (complex)flowchart TD
Start["Consolidate coins"] -->
|Select asset id| get_all_coins("Get coins (all pages)") -->
more_coins_than_max_inputs(count >= max_inputs?) -->
|No| is_base_asset("Consolidating base asset?") -->
|No| get_resources_for_less("Call getResourcesToSpend to fund tx with one base input, all non-base inputs, and two outputs") -->
fill_up_all("Combine received base asset resources and as many non-base asset coins as possible") -->
at_least_two_non_base("There are at least two non-base inputs in the combination?") -->
|Can't consolidate one coin, too many base asset dust coins, consider first consolidating them| throw
at_least_two_non_base -->
|Yes| verify_resources_can_fund("Verify resources can still fund tx after they have been added") -->
|No| refund_tx("Call getResourcesToSpend to fund this updated tx") -->
fill_up_all
verify_resources_can_fund -->
|Yes| prepare_script_tx("Prepare tx with coins, check validity (amount > maxFee)")
is_base_asset -->
|Yes| select_all_coins("Select all coins") -->
prepare_script_tx
more_coins_than_max_inputs -->
| Yes| is_base_asset_2("Consolidating base asset?") -->
|No| get_resources_two_outputs("Call getResourcesToSpend to fund tx that has max fake inputs and two outputs") -->
fill_up_max("Combine funding resources and consolidation coins until max_inputs reached")
is_base_asset_2 -->
|Yes| get_resources_one_output("Call getResourcesToSpend to fund tx that has max fake inputs and one output") -->
fill_up_max("Combine resources for funding and consolidation coins until max_inputs reached") -->
prepare_script_tx
prepare_script_tx -->
|Invalid| throw
prepare_script_tx -->
|Valid| send_tx("Send transaction and await")
send_tx --> |Success| coins_remaining{"Remaining unconsolidated coins > 1?"}
send_tx --> |Failure| throw("Throw, put results of previous transactions on error")
send_tx --> |SqueezedOut| throw
coins_remaining --> |Yes| more_coins_than_max_inputs
coins_remaining --> |No| return_to_user(Return transactions and coins to user)
UTXO consolidation (simple)flowchart TD
Start(Start) -->
select_asset[\Select asset id/] -->
get_all_coins{"get all coins (paginated)"} -->
has_coins([Has more than one unconsolidated coin?]) ---> |No| return@{shape: docs, label: "Return transactions <br>(and all coins?)<br>to user"}
has_coins --> |Yes| consolidating_base_asset([Consolidating base asset?])
consolidating_base_asset -->
|No| consolidate_base_asset((("Consolidate all base asset UTXOs into one"))) -->
|Continue| combine_non_base@{shape: procs, label: "Select one base and <br>(max. inputs - 1) non-base coins"} -->
build_tx((("Build tx and check validity <br>(amount > max fee)")))
consolidating_base_asset -->
|Yes| more_than_max_inputs([coins > max. inputs?]) -->
|No| select_all@{shape: procs, label: "Select all base coins"} --> build_tx
more_than_max_inputs -->
|Yes| get_resources{"Call getResourcesToSpend to fund tx with max inputs"} -->
combine_base@{shape: procs, label: "Combine resources and unconsolidated coins until max. inputs"} -->
build_tx
build_tx -->
|Invalid| throw@{shape: docs, label: "Throw, put results of previous transactions on error"}
build_tx -->
|Valid| submit{"Submit"}
submit -.-> |Success| has_coins
submit --> |Failure| throw
submit --> |SqueezedOut| throw
style consolidate_base_asset fill:none;
style return stroke:green, fill:none, color:green;
style throw stroke:red, fill:none, color:red;
classDef HTTPReq stroke:blue, fill:none, color:blue;
class get_all_coins HTTPReq;
class get_resources HTTPReq;
class submit HTTPReq;
|
Selecting base asset coins in-memory when consolidating non-base assetsThe Base asset coin selection
flowchart TD;
start[Start] --> getOptimal("Get optimal <sup>(1)</sup> base coins")
getOptimal --> amountGteFee1(["amount >= maxFee"])
amountGteFee1 --> agf1Yes(((yes)))
amountGteFee1 --> agf1No(((no)))
agf1Yes --> selectConsolidationCoins("Select consolidation coins <sup>(3)</sup>")
selectConsolidationCoins --> return@{ shape: docs, label: "return — base + consolidation coins"}
agf1No --> getMax("Get least satisfactory <sup>(2)</sup> base coins")
getMax ----> amountGteFee2(["amount >= maxFee"])
amountGteFee2 --> agf2Yes(((yes)))
amountGteFee2 --> agf2No(((no)))
agf2Yes --> selectConsolidationCoins
agf2No --> throw([INSUFFICIENT_FUNDS_OR_MAX_COINS])
getOptimal <-.-> |zooming in| optimalCoinsRoot(((—)))
getMax <-.-> |zooming in| maxCoinsRoot(((—)))
%% Optimal coins selection
optimalCoinsRoot --> sortCoinsAsc(["sort coins (asc)"])
sortCoinsAsc --> iterateOpt["Iterate"]
iterateOpt --> addCoinOpt([add coin]) --> reachedOptimalLength(["selectedCoins.length == optimalCount"])
reachedOptimalLength --> rolYes(((yes)))
reachedOptimalLength --> rolNo(((no)))
rolNo --> addCoinOpt
rolYes --> amountGteFeeOptimal(["amount >= maxFee"])
amountGteFeeOptimal --> agfoYes(((yes)))
amountGteFeeOptimal --> agfoNo(((no)))
agfoYes --> returnOptimal{{return coins}}
agfoNo --> coinsRemaining([coins remaining?])
coinsRemaining --> crNo(((no)))
coinsRemaining --> crYes(((yes)))
crYes --> shiftCoins([shift selectedCoins]) --> addCoinOpt
crNo --> returnOptimal
%% Max coins selection
maxCoinsRoot --> sortCoinsDesc(["sort coins (desc)"])
sortCoinsDesc --> iterateMax[Iterate]
iterateMax --> addCoinMax([add coin]) --> amountGteFeeMax(["amount >= maxFee"])
amountGteFeeMax --> agfmYes(((yes)))
amountGteFeeMax --> agfmNo(((no)))
agfmYes ---> returnMax{{return coins}}
agfmNo --> reachedMaxLength(["selectedCoins.length == maxInputs - 2"])
reachedMaxLength --> rmlYes(((yes)))
reachedMaxLength --> rmlNo(((no)))
rmlYes --> returnMax
rmlNo --> addCoinMax
classDef Error stroke:red, fill:none, color:red;
class throw Error;
classDef ReturnCoins stroke:#00d5ff, fill:none, color:#00d5ff;
class return ReturnCoins;
class returnOptimal ReturnCoins;
class returnMax ReturnCoins;
|
Let's reset the rationale here, get back to simplicity, and try to think of two options. 1. ManuallyThis approach doesn't depend on any new Fuel Core feature.
---
title: UTXO Consolidation
---
flowchart TB
start(start) --> coins{coins}
coins --> greaterThanOne([coins.length > 1])
greaterThanOne --> yes((yes))
greaterThanOne --> no((no))
no --> null@{ shape: flag, label: "return null"}
yes --> estimate{dryRun}
estimate --> fund{coinsToSpend}
fund --> canPay([can cover fee?])
canPay --> canPayNo((no))
canPayNo --> error@{ shape: docs, label: "return error"}
canPay --> canPayYes((yes))
canPayYes --> mergeCoins{{merge coins}}
mergeCoins --> submit{submit}
submit --> failure[\failure\]
failure --> error
submit --> success[/success/]
success --> txs@{ shape: docs, label: "return tx"}
style start stroke: #333333, fill: #ffffff, color: #000000
style null stroke: #cccccc, fill: #333333, color: #cccccc
style txs stroke: #0bebcd, fill: #ffffff, color: #000000
style success stroke: #0bebcd, fill: #0bebcd, color: #000000
style failure stroke: #f500b8, fill: #f500b8, color: #000000
style error stroke: #f500b8, fill: #ffffff, color: #000000
classDef Coins stroke: #00d5ff, fill: #00d5ff, color: #000000
class mergeCoins Coins
classDef HTTPReq stroke: #00ffc3, fill: none, color: #00ffc3
class coins HTTPReq
class estimate HTTPReq
class fund HTTPReq
class submit HTTPReq
2.
|
@arboleya great idea on
What we'd need for this is the following GraphQL API: type ConsolidationTransactionResult {
inputs: [Input!]!
outputs: outputs: [Output!]!
maxFee: U64!
gasLimit: U64!
}
type AssembleConsolidationTxResult {
consolidationTxs: [ConsolidationTransactionResult!]
}
type Query {
assembleConsolidationTxs(
owner: Address!
assetId: AssetId!
predicate: HexString
predicateData: HexString
} Then, based on this data the query returns, our method on our const { txRequests, submitAll, beforeCount, afterCount, totalFee } =
await wallet.assembleConsolidationTxs({ assetId: '0x123' }); Which is enough info for browser wallets to use to create consolidation flows. |
@nedsalk It's always easier to deal with Can you please specify another change about adding a new Please, rely on @Torres-ssf to give the final word on the spec to ensure it is as similar as possible with |
That change would be made on the type Balance {
owner: Address!
amount: U64!
amountU128: U128!
assetId: AssetId!
utxosNum: U64!
} |
TODOs:
|
Moved the last two tasks to: |
Problem
N
inputsN
coinsActionable Items
We need to think about two phases:
split
andconsolidation
Provide an automated flow to consolidate coins on the fly during transaction funding/submissionAutomatic UTXO Consolidation
Because UTXO consolidation will use gas, it's tricky to enable it by default.
However, we could..
..offer an extra submission method or add parameters to the existing one.
The idea is to have the flow available, but it should be opt-in.
Here's a first sketch:
The text was updated successfully, but these errors were encountered: