Skip to content

Commit

Permalink
Fix claim_outputs when no basic outputs are available (#1885)
Browse files Browse the repository at this point in the history
  • Loading branch information
Thoralf-M authored Feb 23, 2023
1 parent f5a8b5b commit 33069b6
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .changes/claimoutputs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nodejs-binding": patch
---

Fix `Account::claimOutputs()` when no basic outputs are available.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `Account::get_participation_overview()` with multiple events and different nodes;
- `prepare_nft_output` uses newly provided tag/metadata instead of previous ones from unspent output;
- `Account::claim_outputs()` when the account has no basic outputs available;

## 1.0.0-rc.5 - 2023-02-09

Expand Down
6 changes: 3 additions & 3 deletions bindings/nodejs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 22 additions & 5 deletions bindings/nodejs/examples/26-send-nft.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ const getUnlockedManager = require('./account-manager');
async function run() {
try {
const { initLogger } = require('@iota/wallet');
initLogger({
name: './wallet.log',
levelFilter: 'debug',
targetExclusions: ["h2", "hyper", "rustls"]
});
initLogger({
name: './wallet.log',
levelFilter: 'debug',
targetExclusions: ["h2", "hyper", "rustls"]
});
const manager = await getUnlockedManager();

const account = await manager.getAccount('0');
Expand All @@ -25,11 +25,28 @@ async function run() {
nftId: '0x09aa7871e126cc41f1f3784a479a5dd5f23e4dd8b97e932a001e77a11ad42f0c',
}]);



console.log(response);

console.log(
`Check your block on ${process.env.NODE_URL}/api/core/v2/blocks/${response.blockId}`,
);

// To send an NFT with expiration unlock condition prepareOutput() can be used like this:
// const output = await account.prepareOutput({
// recipientAddress: 'rms1qz6aj69rumk3qu0ra5ag6p6kk8ga3j8rfjlaym3wefugs3mmxgzfwa6kw3l',
// amount: "47000",
// unlocks: {
// expirationUnixTime: 1677065933
// },
// assets: {
// nftId: '0x447b20b81e2311a6c16a32eaeda2f2f2472c4b43ed4ffc80a0c0f850130fc4bb',
// },
// storageDeposit: { returnStrategy: 'Gift' }
// });

// const transaction = await account.sendOutputs([output]);
} catch (error) {
console.log('Error: ', error);
}
Expand Down
59 changes: 40 additions & 19 deletions src/account/operations/output_claiming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,15 +259,26 @@ impl AccountHandle {
// build new output with same amount, nft_id, immutable/feature blocks and native tokens, just
// updated address unlock conditions

let nft_output = NftOutputBuilder::from(nft_output)
.with_minimum_storage_deposit(rent_structure.clone())
.with_nft_id(nft_output.nft_id_non_null(&output_data.output_id))
.with_unlock_conditions([UnlockCondition::Address(AddressUnlockCondition::new(
first_account_address.address.inner,
))])
// Set native tokens empty, we will collect them from all inputs later
.with_native_tokens([])
.finish_output(token_supply)?;
let nft_output = if possible_additional_inputs.is_empty() {
// Only update address and nft id if we have no additional inputs which can provide the storage
// deposit for the remaining amount and possible NTs
NftOutputBuilder::from(nft_output)
.with_nft_id(nft_output.nft_id_non_null(&output_data.output_id))
.with_unlock_conditions([UnlockCondition::Address(AddressUnlockCondition::new(
first_account_address.address.inner,
))])
.finish_output(token_supply)?
} else {
NftOutputBuilder::from(nft_output)
.with_minimum_storage_deposit(rent_structure.clone())
.with_nft_id(nft_output.nft_id_non_null(&output_data.output_id))
.with_unlock_conditions([UnlockCondition::Address(AddressUnlockCondition::new(
first_account_address.address.inner,
))])
// Set native tokens empty, we will collect them from all inputs later
.with_native_tokens([])
.finish_output(token_supply)?
};

// Add required amount for the new output
required_amount_for_nfts += nft_output.amount();
Expand All @@ -282,8 +293,12 @@ impl AccountHandle {
};

// Check if the new amount is enough for the storage deposit, otherwise increase it to this
let mut required_amount = required_amount_for_nfts
+ minimum_storage_deposit_basic_output(&rent_structure, &option_native_token, token_supply)?;
let mut required_amount = if possible_additional_inputs.is_empty() {
required_amount_for_nfts
} else {
required_amount_for_nfts
+ minimum_storage_deposit_basic_output(&rent_structure, &option_native_token, token_supply)?
};

let mut additional_inputs = Vec::new();
if available_amount < required_amount {
Expand Down Expand Up @@ -343,14 +358,20 @@ impl AccountHandle {
}

// Create output with claimed values
outputs_to_send.push(
BasicOutputBuilder::new_with_amount(available_amount - required_amount_for_nfts)?
.add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
first_account_address.address.inner,
)))
.with_native_tokens(new_native_tokens.finish()?)
.finish_output(token_supply)?,
);
if available_amount - required_amount_for_nfts > 0 {
outputs_to_send.push(
BasicOutputBuilder::new_with_amount(available_amount - required_amount_for_nfts)?
.add_unlock_condition(UnlockCondition::Address(AddressUnlockCondition::new(
first_account_address.address.inner,
)))
.with_native_tokens(new_native_tokens.finish()?)
.finish_output(token_supply)?,
);
} else if !new_native_tokens.finish()?.is_empty() {
return Err(crate::Error::Client(
iota_client::Error::NoBalanceForNativeTokenRemainder.into(),
));
}

let claim_tx = self
.finish_transaction(
Expand Down
Loading

0 comments on commit 33069b6

Please sign in to comment.