Skip to content

Conversation

kiseln
Copy link
Contributor

@kiseln kiseln commented Sep 12, 2025

No description provided.

@kiseln kiseln force-pushed the feat/fast-transfers-for-utxo branch from 81bffe2 to a0e51cc Compare September 12, 2025 15:14
// Verify this is indeed a UTXO chain token
let _ = self
.get_utxo_chain_by_token(&token_id)
.sdk_expect("ERR_TOKEN_NOT_FROM_UTXO_CHAIN");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The utxo_transfer_msg contains the Omni token address:

  • This address can be used to optimize the get_utxo_chain_by_token call.
  • We have to verify that the token address in the msg is correct to avoid vulnerabilities

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually a mistake, I believe we can remove the OmniAddress. Sender doesn't have to know what the address on Near is

fee: utxo_transfer_msg.fee,
native_fee: U128(0),
},
sender: OmniAddress::Near(env::predecessor_account_id()), // TODO who is the sender?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, the UTXO connector address could be the sender.
Later we can consider to add the sender to the msg, this will be needed if external actions is needed to be done by the user (eg sign custom message using Zcash wallet like in near-intents)

if self.is_transfer_finalised(fast_transfer.transfer_id) {
env::panic_str("ERR_TRANSFER_ALREADY_FINALISED");
}
// if self.is_transfer_finalised(fast_transfer.transfer_id) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check should be done for both "to near" and "to other chain". The transaction can be delayed due to networks or chains issues, so we have to protect the relayer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brought it back 8449952. Two points:

  1. It's convenient to have it at this level but there is a small chance of race condition because fast transfer will be executed in the next block (due to storage check)
  2. We currently don't need to store UTXO transfer id because duplicates are checked on connector level. But we have to add it for this part of the code if we want extra protection


#[near(serializers=[borsh, json])]
#[derive(Debug, Clone)]
pub enum UnifiedTransferId {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@frolvanya please look into this new structure if it fits the indexer requirements

Comment on lines 914 to 923
return self
.send_tokens(token_id, status.relayer, amount, &String::new())
.into();
}

if let OmniAddress::Near(recipient) = utxo_transfer_msg.recipient {
// Fast transfer didn't happen so we send the user full amount including fee
return self
.send_tokens(token_id, recipient, amount, &utxo_transfer_msg.msg)
.into();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The send_tokens call isn't async, this mean that it's results affect the refund for the top ft_tranasfer_call.
For example if the msg is empty then the last promise will be ft_transfer with empty results.
Also there are edge cases for ft_transfer_call panics and returned values.
We have to cover all of that with tests.
Also it will be good to verify the storage deposit for the the recipient

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the call fails we are okay to refund the tokens.

Two questions to answer here are:

  1. What happens if empty result is returned
  2. Whether we can use ft_transfer_call return value. I believe here the answer is no because it will return used_amount while we want to return 0 in case of success

Is my understanding correct? If so, I'll add a callback to handle it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some changes here 2537719

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to validate storage for the recipient? If the transfer fails we would handle the refund properly

Copy link
Collaborator

@karim-en karim-en Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to validate storage for the recipient? If the transfer fails we would handle the refund properly

Not really, depends on how you are going to handle the refund.
For example for the ft_transfer it is ok to refund if the receipt failed, but it isn't safe to refund if the ft_transfer_call failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it isn't safe because of potential out of gas issue?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, correct. We have 2 options here:

  • verify the storage deposit before the ft_transfer_call.
  • modify the token contract, so the ft_transfer_call will returns value instead of panic of the account isn't registered

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added ade99b2

pub fee: U128,
pub native_token_fee: U128,
pub msg: Option<String>,
pub msg: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why msg not Option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not option in InitTransferMessage either. We use empty strings

BridgeOnTransferMsg::FastFinTransfer(fast_fin_transfer_msg) => {
self.fast_fin_transfer(token_id, amount, signer_id, fast_fin_transfer_msg)
}
BridgeOnTransferMsg::UtxoTransfer(utxo_transfer_msg) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
BridgeOnTransferMsg::UtxoTransfer(utxo_transfer_msg) => {
BridgeOnTransferMsg::TransferFromUtxo(utxo_transfer_msg) => {

What about a name like this? Otherwise, it seems like these arguments need to be passed when transferring to a UTXO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed yours is better. Also thinking about UtxoFinTransfer or FinTransferFromUtxo

@kiseln kiseln marked this pull request as ready for review September 29, 2025 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants