ZaStoGram_desktop/Telegram/SourceFiles/wallet/wallet_session.h
loop-uh 0fd5d76a67 Merge upstream v7.3.1
Слияние с tdesktop от 64ca5475f2 до тега v7.3.1 (7.2.10 beta, 7.3, 7.3.1).

- Версия 7.3.1 (7003001), BetaChannel 0, комментарий ZaStoGram сохранён.
- Обновлятор: оригинал убрал пакеты v1 (RSA), а наш Packer и Forgejo
  выпускают именно их. v1 оставлен как UnpackUpdateV1 рядом с v2 и
  использует новые проверенные распаковку и извлечение файлов.
- mtproto: исправления импорта авторизации из оригинала перенесены в нашу
  структуру RequestRegistry; пути include новых файлов кошелька
  приведены к нашей раскладке mtproto/.
- prepare.py: стадии оригинала (ucrt64, ffmpeg 8, openssl 3.5, libheif,
  Rust, wallet-engine) с нашими skip-debug и patches_extra.
- lib_ui, lib_lottie: форки слиты с версиями из v7.3.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:43:47 +03:00

1358 lines
46 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/flat_map.h"
#include "base/timer.h"
#include "core/core_cloud_password.h"
#include "data/data_msg_id.h"
#include "data/data_peer_id.h"
#include "gram/api/gram_api_account.h"
#include "gram/api/gram_api_nft.h"
#include "mtproto/instance/sender.h"
#include "wallet/wallet_address.h"
#include "wallet/wallet_api.h"
#include "wallet/wallet_custody.h"
#include "wallet/wallet_engine.h"
#include "wallet/wallet_stream.h"
#include "wallet/wallet_transfer_store.h"
#include "wallet/wallet_unlock.h"
namespace wallet_engine {
struct ActivityItem;
struct SendMessageBody;
struct SendSnapshot;
struct WalletClient;
struct WalletDescriptor;
struct WalletUpdate;
} // namespace wallet_engine
namespace Main {
class Account;
class Session;
} // namespace Main
namespace Ui {
class SeparatePanel;
} // namespace Ui
namespace Wallet {
class Engine;
class Onramp;
class Rates;
class Session;
struct ShareFetch;
class TonConnect;
enum class TonConnectAccess : uchar;
struct TonConnectAppRequest;
struct TonConnectEmulation;
struct TonConnectEventRequest;
struct TonConnectKey;
enum class TonConnectKeyError : uchar;
struct TonConnectReply;
struct TonConnectResponse;
struct TonConnectSignData;
struct TonConnectTransfer;
class TransferMessages;
struct TransferSubmissionAnswer;
struct TransferSubmissionData;
class UserAddresses;
class VaultRuntime;
enum class Presence {
Unknown,
Provisioning,
Missing,
Unavailable,
AddressUnreadable,
Ready,
};
struct WalletCapabilities {
bool backupEnabled = false;
bool canExportPhrase = false;
bool canEnableBackup = false;
friend bool operator==(
const WalletCapabilities &,
const WalletCapabilities &) = default;
};
enum class DeviceMode {
Unknown,
Full,
ReadOnlyRestorable,
ReadOnlyNotRestorable,
};
struct DeviceCustodyState {
DeviceMode mode = DeviceMode::Unknown;
bool conflict = false;
friend bool operator==(
const DeviceCustodyState &,
const DeviceCustodyState &) = default;
};
// The two terms the overview's empty face takes from the lists gate. One
// updateListsGate() call can change both, and the face is derived through a
// single rpl::combine argument, so publishing them as one value is what
// keeps a face from being resolved once from a new term against a stale one
// before the second arrives.
struct ListsEmptyState {
bool confirmedEmpty = false;
bool unreachable = false;
friend bool operator==(
const ListsEmptyState &,
const ListsEmptyState &) = default;
};
enum class AccountStatus {
NonExisting,
Uninit,
Active,
Frozen,
};
enum class SendError {
None,
InvalidRequest,
AmountTooSmall,
CommentTooLong,
CommentEncryptionUnavailable,
InsufficientBalance,
InsufficientFees,
PreviousUnresolved,
AlreadySending,
SigningUnavailable,
Locked,
Failed,
Rejected,
DataInvalid,
KeyMismatch,
KeyChanged,
QuoteExpired,
LinkExpired,
CollectibleUnavailable,
CollectibleRejected,
Silent,
// The send callback's third outcome beside None and a refusal:
// the signed message is journaled and may already be on the
// network, so nothing was refused and nothing is known to have
// been delivered. The engine's resolver settles it later; a
// surface presents it as pending, never as sent, never as failed.
SubmissionUnknown,
};
enum class CommentRecipient {
Encryptable,
PlainOnly,
Unknown,
};
enum class DnsLookupError {
Busy,
Failed,
};
enum class SendState {
Idle,
Sending,
Pending,
};
struct TransferWalletIdentity {
QString address;
QByteArray publicKey;
uint64 revision = 0;
friend bool operator==(
const TransferWalletIdentity &,
const TransferWalletIdentity &) = default;
};
struct BackupDisableApproval {
QString address;
QString recordId;
int networkGeneration = 0;
};
struct TransferItem {
enum class Source {
Unknown,
Server,
Engine,
};
enum class EncryptedFormat {
Unavailable,
ServerPayload,
EngineBodyBoc,
};
enum class Status {
Success,
Failure,
Pending,
};
enum class Kind {
Transfer,
Collectible,
Onramp,
PeerTransfer,
KeyChange,
};
Source source = Source::Unknown;
QString id;
std::optional<TransferWalletIdentity> walletIdentity;
Kind kind = Kind::Transfer;
bool incoming = false;
QString counterparty;
bool counterpartyBounceable = false; // as served or as sent, raw is UQ
QString counterpartyName;
quint64 counterpartyPeer = 0;
QString collectible;
std::optional<Gram::NftItem> collectibleRecord;
QString provider;
// Always a magnitude. The mappings fold a source's signed amount into
// this field and `incoming`, because every surface that paints a
// transfer prefixes a direction character of its own and would
// otherwise render two signs.
int64 amountNano = 0;
std::optional<int64> feeNano;
bool gasless = false;
QString comment;
bool commentEncrypted = false;
EncryptedFormat encryptedFormat = EncryptedFormat::Unavailable;
QByteArray encryptedPayload;
std::optional<TimeId> date;
quint64 lt = 0;
QByteArray traceId;
QByteArray externalHashNorm;
QString failureReason;
Status status = Status::Success;
friend bool operator==(
const TransferItem &,
const TransferItem &) = default;
};
// The session binds a scope before prompts to one immutable transaction and
// presentation lifetime. cancel() and Session's scope checks run on main;
// the worker observes only its atomic retirement flag and the vault epoch.
// Retirement cancels owned recovery transport, while an import already in
// flight remains owned through rollback and custody-latch settlement.
class CommentScope final {
public:
void cancel();
[[nodiscard]] bool cancelled() const;
[[nodiscard]] rpl::producer<> cancelledChanges() const;
private:
friend class Session;
struct State;
explicit CommentScope(std::shared_ptr<State> state);
const std::shared_ptr<State> _state;
rpl::event_stream<> _cancelledChanges;
};
enum class CommentDecryptError {
None,
Cancelled,
Unavailable,
Locked,
// The host could not read the stored key at all.
KeyUnreadable,
// The key was read and does not open this comment.
DecryptionFailed,
// The engine's one resolution slot was taken. The session retries a
// bounded number of times and never surfaces this value itself.
Busy,
Failed,
};
struct CommentDecryptResult {
QString text;
CommentDecryptError error = CommentDecryptError::None;
};
// The answer to one transaction lookup. An empty |item| with |failed| false
// is the server not naming that transaction yet, which is a transfer whose
// message arrived before it was indexed, and not the same as a refusal.
struct ResolvedTransaction {
std::optional<TransferItem> item;
bool failed = false;
};
inline constexpr auto kTransferMinNanosDefault = int64(100'000'000);
inline constexpr auto kTransferMinNanosMax = (int64(1) << 53);
inline constexpr auto kGaslessMinNanosDefault = int64(100'000'000);
struct GaslessInfo {
QString relayer;
int64 minAmount = 0;
TimeId resetAt = 0;
int left = 0;
bool available = false;
friend bool operator==(const GaslessInfo &, const GaslessInfo &) = default;
};
struct GaslessTerms {
std::optional<TransferWalletIdentity> identity;
std::optional<GaslessInfo> info;
int64 transferMinNanos = kTransferMinNanosDefault;
int64 configuredMinNanos = kGaslessMinNanosDefault;
int64 effectiveMinNanos = kGaslessMinNanosDefault;
uint64 revision = 0;
bool fresh = false;
bool usable = false;
[[nodiscard]] bool eligible(int64 amountNano) const;
[[nodiscard]] bool eligible(
int64 amountNano,
const QString &destination) const;
friend bool operator==(const GaslessTerms &, const GaslessTerms &) = default;
};
struct PreparedSend;
struct FeeResult {
int64 feeNano = 0;
SendError error = SendError::None;
// Null for a private comment priced without the key: a fee, no transfer.
std::shared_ptr<const PreparedSend> prepared;
std::shared_ptr<const TonConnectEmulation> emulation;
};
struct PendingSendInfo {
std::string operationId;
TransferWalletIdentity walletIdentity;
TimeId posted = 0;
int64 amountNano = 0;
QString destination;
QString collectible;
QString comment;
UserId recipient;
bool bounce = false;
};
[[nodiscard]] TransferItem ItemFromPending(const PendingSendInfo &pending);
struct SendStarted {
std::string operationId;
FullMsgId message;
};
struct ListedSubmittedTransfer {
std::string operationId;
TransferItem item;
};
struct TransferReceipt {
QByteArray messageHash;
bool gasless = false;
};
struct TonConnectSendResult {
QString signedBoc;
SendError error = SendError::None;
};
struct TonConnectSendLink {
std::string operationId;
Fn<bool(const QString &signedBoc)> handoff;
};
enum class TonConnectSendFate : uchar {
Unknown,
Absent,
Unresolved,
NotExecuted,
Settled,
Sending,
};
struct SendComment {
QString text;
bool isPublic = false;
friend bool operator==(const SendComment &, const SendComment &) = default;
};
[[nodiscard]] int SendCommentBytes(const QString &text);
[[nodiscard]] bool SendCommentFits(const QString &text);
[[nodiscard]] int64 CollectibleTransferAttachedNanos();
[[nodiscard]] int64 TransferMinNanosFromConfig(float64 configured);
[[nodiscard]] int64 TransferMinNanos(not_null<Main::Session*> session);
[[nodiscard]] bool TransferAmountBelowMinimum(
int64 amountNano,
int64 minNanos);
// One policy read from two sides. A send judges the positive amount the
// user asked to transfer, so its entry keeps a `> 0` guard of its own; a
// history row judges a magnitude, because a transfer's sign is only its
// direction and the stored amount has already been folded to a positive
// one. Both sides ask the same comparison, and it is stated once, in the
// magnitude predicate the other two are written in terms of.
[[nodiscard]] bool TransferMagnitudeBelowMinimum(
int64 amountNano,
int64 minNanos);
[[nodiscard]] bool HistoryTransferHidden(
const TransferItem &item,
int64 minNanos);
struct SendArgs {
QString destination;
// An owned collectible's item address; the session picks amountNano then.
QString collectible;
int64 amountNano = 0;
UserId userId;
SendComment comment;
// The recipient's Ed25519 public key, when Telegram named one for this
// destination. A private comment encrypts for it without the recipient's
// `get_public_key`, which a wallet that was never deployed cannot answer.
QByteArray recipientPublicKey;
bool bounce = true;
friend bool operator==(const SendArgs &, const SendArgs &) = default;
};
[[nodiscard]] QByteArray DecodeServerEncryptedComment(const QString &encoded);
// The three states a private comment is laid out in, all decided by the
// message bytes alone, so a surface settles them while it builds the
// comment instead of offering a reveal that no key could ever serve.
// Pending is this device's own transfer before the server named it: it
// carries the text the user typed and no payload, because only the served
// transaction carries one. Unusable is a payload that cannot be decrypted
// by anything - the sender address does not parse, the transaction is
// named by nothing, or the body fails its structural parse. Revealable is
// the rest, and the only one a spoiler belongs on.
[[nodiscard]] bool EncryptedCommentPending(const TransferItem &item);
[[nodiscard]] bool EncryptedCommentUnusable(const TransferItem &item);
[[nodiscard]] bool EncryptedCommentRevealable(const TransferItem &item);
// The 32 hash bytes in base64 or hex, or nothing for another shape.
[[nodiscard]] QByteArray TransactionHashFromServer(const QString &value);
[[nodiscard]] std::vector<TransferItem> HistoryFromEngine(
const std::vector<wallet_engine::ActivityItem> &items,
std::optional<TransferWalletIdentity> identity = std::nullopt);
[[nodiscard]] std::vector<TransferItem> HistoryFromServer(
const QVector<MTPWalletTransaction> &list,
std::optional<TransferWalletIdentity> identity = std::nullopt);
[[nodiscard]] std::vector<Gram::NftItem> CollectiblesFromServer(
const QVector<MTPwallet_NftItem> &list);
[[nodiscard]] bool IsWordlistWord(const QString &word);
[[nodiscard]] std::vector<QString> WordlistSuggestions(
const QString &prefix,
int limit);
enum class PhraseMatch {
Rotation,
Foreign,
None,
};
[[nodiscard]] PhraseMatch DetectPhraseMatch(const std::vector<QString> &words);
struct PhraseIdentity {
QByteArray anchor;
QByteArray signing;
};
// What logging out of an account, or removing its keys after a forgotten
// passcode, would destroy on this device, read from its custody store:
// `unbacked` counts the served wallet when Telegram holds no backup of it,
// `parked` counts every record of another wallet, `rotating` counts
// a key change this device started and nothing has confirmed - its
// replacement key is named by no record yet, so a promotion is what would put
// it under a record, and no backup can cover it before that promotion, which
// is why it is neither of the first two - and `unknown` says the account's
// loss cannot be stated as a fact, because the custody store could not be read
// or the served wallet's state has not reached this client. A record of the
// served wallet that holds an obsolete signing key is no loss: the key no
// longer signs, so nothing it could reveal is current. A pre-v4 record of the
// served wallet, whose signing key was never established, counts as active,
// because it may be the only copy of the current phrase. `holdsRecords` is
// not a loss: it says whether the store held any record at all, which is what
// tells an empty loss "every record is backed" apart from "there is nothing
// here to lose", and carrying it is what lets the forgot confirmation decide
// that without reading the store a second time. Two invariants a reviewer must
// be able to check by reading the bodies: nothing here unlocks a vault or
// reads a secret, and nothing here writes.
//
// Two renderers state this model, and they are declared and defined next to
// each other below for the reason a comment could not enforce: a term given to
// one and forgotten in the other silently drops a sentence from a
// confirmation, and the one that would lose it is the irreversible one.
// add() is the third place a term has to be taught, and it is a member so that
// the whole-domain confirmation cannot sum the fields by hand again.
struct WalletLoss {
int unbacked = 0;
int parked = 0;
int rotating = 0;
bool holdsRecords = false;
bool unknown = false;
void add(const WalletLoss &other);
};
[[nodiscard]] WalletLoss WalletLossOnLogout(
not_null<Main::Account*> account);
[[nodiscard]] QString WalletLossWarning(WalletLoss loss);
class Session final {
public:
explicit Session(not_null<Main::Session*> session);
~Session();
[[nodiscard]] Presence presence();
[[nodiscard]] Presence presenceCurrent() const;
[[nodiscard]] rpl::producer<Presence> presenceValue();
[[nodiscard]] std::optional<QString> address();
[[nodiscard]] QString addressFriendly(bool bounceable = false);
[[nodiscard]] WalletCapabilities capabilities() const;
[[nodiscard]] rpl::producer<WalletCapabilities> capabilitiesValue() const;
// The served wallet's current on-chain signing key, not the anchor.
[[nodiscard]] QByteArray publicKey() const;
[[nodiscard]] auto transferWalletIdentity() const
-> std::optional<TransferWalletIdentity>;
[[nodiscard]] bool transferWalletIdentityCurrent(
const TransferWalletIdentity &identity) const;
[[nodiscard]] rpl::producer<> transferWalletIdentityChanges() const;
// A record-bound engine client that has finished recovering its send
// journal: the state send() requires beyond a valid authorization. A
// public-key-only client previews fees but never counts as ready.
[[nodiscard]] bool signingReady() const;
[[nodiscard]] rpl::producer<bool> signingReadyValue() const;
[[nodiscard]] bool revealsLocally();
[[nodiscard]] std::optional<BackupDisableApproval> backupDisableApproval();
[[nodiscard]] VaultRuntime &vault() const;
[[nodiscard]] DeviceCustodyState deviceCustodyState() const;
[[nodiscard]] auto deviceCustodyStateValue() const
-> rpl::producer<DeviceCustodyState>;
[[nodiscard]] rpl::producer<> custodyUpdates() const;
[[nodiscard]] bool custodyBusy() const;
// Everything createCommentScope() asks of the wallet, with nothing about
// the message it would be opened over. A surface waiting for a comment
// to become openable waits on this, so it waits for the same state the
// scope will be made in instead of one the next call disproves.
[[nodiscard]] bool commentAccessReady() const;
// Protection and unusable state belong to the whole domain. Every session
// observes the same event and updates only its already-cached device mode.
// Announcing a change neither opens D nor reads or writes custody, and
// never reconciles a passcode while an install is only armed. A failed
// reset can preserve unusable state when it announces its terminal result.
[[nodiscard]] rpl::producer<> keyProtectionUpdates() const;
void notifyKeyProtectionChanged(bool vaultKeyStillUnusable = false);
void resetCustodyAfterForgottenPasscode(Fn<void(CustodyResetResult)> done);
// The shared runtime marks all accounts read-only when the current live
// factor is Absent, Unavailable or Corrupt. This state never edits disk;
// the existing confirmed reset owns deletion. An authentication failure
// or cancellation does not set it, and a stale factor with no live entry
// cannot prevent a fresh install from choosing its own protection.
[[nodiscard]] bool vaultKeyUnusable() const;
void setVaultKeyUnusable(bool unusable);
[[nodiscard]] std::vector<CustodyRecord> parkedRecords();
void ensureLoaded();
void refreshState();
// Asks Telegram for one transaction by the id a message carried, for a
// surface that has only what that message said about it. |done| runs
// exactly once. A transaction the server does not name yet answers with
// an empty |item| and |failed| false, which is what a just-sent transfer
// looks like until it is indexed, so a caller can ask again; a request
// that failed answers |failed| true. Destruction retires the request
// without running |done|.
void resolveTransaction(
const QString &id,
Fn<void(ResolvedTransaction)> done);
void applyUpdate(const MTPDupdateWalletState &data);
void applyUpdate(const MTPDupdateSentWalletTransaction &data);
void applyUpdate(const MTPDupdateWalletGaslessInfo &data);
void applyUpdate(const MTPDupdateWalletTonConnectSession &data);
void applyUpdate(const MTPDupdateWalletTonConnectPendingDisconnect &data);
[[nodiscard]] std::shared_ptr<CommentScope> createCommentScope(
TransferItem target,
rpl::lifetime &lifetime);
[[nodiscard]] bool commentScopeCurrent(
const std::shared_ptr<CommentScope> &scope) const;
void decryptComment(
KeyAuthorization auth,
std::shared_ptr<CommentScope> scope,
Fn<void(CommentDecryptResult)> done);
void revealPhrase(
KeyAuthorization auth,
std::optional<Core::CloudPasswordResult> password,
Fn<void(std::vector<QString>, CustodyOutcome outcome)> done,
Fn<void(const QString &error)> fail,
Fn<void()> authorized = nullptr);
// Both replace shapes share one done at their single caller, so they
// carry one callback type. Only an imported replace runs a custody
// install, so only it can answer WriteFailed; a create replace has no
// custody install to report and always answers Installed, the value
// every consumer reads as the success.
void replaceWithNew(
std::optional<Core::CloudPasswordResult> password,
Fn<void(CustodyOutcome)> done,
Fn<void(const QString &error)> fail);
void replaceWithImported(
KeyAuthorization auth,
std::vector<QString> words,
std::optional<Core::CloudPasswordResult> password,
Fn<void(CustodyOutcome)> done,
Fn<void(const QString &error)> fail);
void restoreFromPhrase(
KeyAuthorization auth,
std::vector<QString> words,
Fn<void()> done,
Fn<void(const QString &error)> fail);
void restoreFromBackup(
KeyAuthorization auth,
std::optional<Core::CloudPasswordResult> password,
Fn<void()> done,
Fn<void(const QString &error)> fail);
void restoreFromPhrase(
KeyAuthorization auth,
std::vector<QString> words,
std::shared_ptr<CommentScope> scope,
Fn<void(KeyAuthorization)> done,
Fn<void(const QString &error)> fail);
void restoreFromBackup(
KeyAuthorization auth,
std::optional<Core::CloudPasswordResult> password,
std::shared_ptr<CommentScope> scope,
Fn<void(KeyAuthorization)> done,
Fn<void(const QString &error)> fail);
void revealParked(
KeyAuthorization auth,
const QByteArray &publicKey,
Fn<void(std::vector<QString>, CustodyOutcome outcome)> done,
Fn<void(const QString &error)> fail);
void dropParked(
const QByteArray &publicKey,
Fn<void()> done,
Fn<void(const QString &error)> fail);
void enableBackup(
KeyAuthorization auth,
Fn<void()> done,
Fn<void(const QString &error)> fail);
void disableBackup(
std::optional<Core::CloudPasswordResult> password,
Fn<void()> done,
Fn<void(const QString &error)> fail);
void disableBackupWithProof(
KeyAuthorization auth,
BackupDisableApproval approved,
Fn<void()> done,
Fn<void(const QString &error)> fail);
[[nodiscard]] TonConnectAccess tonConnectAccess();
[[nodiscard]] bool tonConnectProofDomainAllowed(
const QString &domain) const;
void deriveTonConnectSession(
KeyAuthorization auth,
const QString &dappClientId,
const QByteArray &nonce,
Fn<void(TonConnectKey)> done,
Fn<void(TonConnectKeyError)> fail);
void prepareTonConnectEvent(
KeyAuthorization auth,
TonConnectKey key,
TonConnectEventRequest request,
Fn<void(TonConnectReply)> done,
Fn<void(TonConnectKeyError)> fail);
void prepareTonConnectError(
TonConnectKey key,
QByteArray challenge,
uint64 eventId,
int code,
Fn<void(TonConnectReply)> done,
Fn<void()> fail);
void prepareTonConnectDisconnect(
TonConnectKey key,
uint64 eventId,
Fn<void(QByteArray)> done,
Fn<void()> fail);
void decryptTonConnectRequest(
TonConnectKey key,
QByteArray body,
Fn<void(TonConnectAppRequest)> done,
Fn<void()> fail);
void answerTonConnectChallenge(
TonConnectKey key,
QByteArray challenge,
Fn<void(QByteArray)> done,
Fn<void()> fail);
void encryptTonConnectResponse(
TonConnectKey key,
QString requestId,
TonConnectResponse response,
Fn<void(QByteArray)> done,
Fn<void()> fail);
void signTonConnectData(
KeyAuthorization auth,
TonConnectKey key,
QString requestId,
std::shared_ptr<const TonConnectSignData> data,
QString domain,
Fn<void(QByteArray)> done,
Fn<void(TonConnectKeyError)> fail);
[[nodiscard]] bool rotationOffered();
void quoteRotationFee(KeyAuthorization auth, Fn<void(FeeResult)> done);
void prepareRotation(
KeyAuthorization auth,
int64 quotedFeeNano,
Fn<void(std::vector<QString>)> done,
Fn<void(const QString &error)> fail);
void abandonRotation();
void submitRotation(
KeyAuthorization auth,
Fn<void()> confirmed,
Fn<void(const QString &error)> fail);
[[nodiscard]] int64 balanceNano() const;
[[nodiscard]] rpl::producer<int64> balanceNanoValue() const;
[[nodiscard]] rpl::producer<bool> stateKnownValue() const;
[[nodiscard]] AccountStatus status() const;
[[nodiscard]] const std::vector<TransferItem> &history() const;
[[nodiscard]] rpl::producer<> historyUpdates() const;
[[nodiscard]] int64 transferMinNanos() const;
[[nodiscard]] bool historyItemHidden(const TransferItem &item) const;
[[nodiscard]] bool historyVisibleEmpty() const;
[[nodiscard]] bool listsGated() const;
[[nodiscard]] rpl::producer<bool> listsGatedValue() const;
[[nodiscard]] rpl::producer<ListsEmptyState> listsEmptyStateValue() const;
void refreshHistory(Fn<void()> done = nullptr);
[[nodiscard]] bool historyHasNext() const;
[[nodiscard]] bool historyCanPage() const;
[[nodiscard]] bool historyLoadingMore() const;
[[nodiscard]] rpl::producer<bool> historyLoadingMoreValue() const;
void loadMoreHistory();
void resetHiddenHistoryPages();
[[nodiscard]] const std::vector<Gram::NftItem> &collectibles() const;
[[nodiscard]] rpl::producer<> collectiblesUpdates() const;
[[nodiscard]] bool collectiblesTab() const;
[[nodiscard]] rpl::producer<bool> collectiblesTabValue() const;
void setCollectiblesTab(bool value);
[[nodiscard]] bool collectiblesHasNext() const;
void loadMoreCollectibles();
void resolveCollectibleInfo(
const QString &item,
Fn<void(const Gram::NftItem &)> done);
#ifdef _DEBUG
void debugClearNetworkState();
void debugRestoreNetworkState();
#endif
void startPolling();
void stopPolling();
[[nodiscard]] bool pollingRequested() const;
[[nodiscard]] Onramp &onramp();
[[nodiscard]] Rates &rates();
[[nodiscard]] TransferMessages &transferMessages();
[[nodiscard]] UserAddresses &userAddresses();
[[nodiscard]] TonConnect &tonConnect();
[[nodiscard]] Ui::SeparatePanel *panel() const;
void setPanel(std::unique_ptr<Ui::SeparatePanel> panel);
[[nodiscard]] GaslessTerms gaslessTerms();
[[nodiscard]] rpl::producer<GaslessTerms> gaslessTermsValue();
void refreshGaslessInfo(bool force = false);
// Empty while there is no Walt balance to show, otherwise its link.
[[nodiscard]] QString existingWaltBalanceUrl() const;
[[nodiscard]] rpl::producer<QString> existingWaltBalanceUrlValue();
[[nodiscard]] rpl::producer<std::optional<int64>> parkedBalanceNanoValue();
// One live send box owns one preview identity until its lifetime ends.
// Edits replace only that owner's queued request, preserving its place
// behind other owners; cancellation discards its current request without
// retiring the owner. Current callbacks can complete synchronously with
// a refusal, and never run after the owning lifetime is destroyed.
[[nodiscard]] uint64 createPreviewOwner(rpl::lifetime &lifetime);
void estimateFee(
KeyAuthorization auth,
uint64 owner,
const SendArgs &args,
Fn<void(FeeResult)> done);
void cancelFeeEstimate(uint64 owner);
// Asks no key; Unknown is a provider that never answered, not a refusal.
void resolveCommentRecipient(
const QString &destination,
bool bounce,
const QByteArray &recipientPublicKey,
Fn<void(CommentRecipient)> done);
// Asks no key; |done| gets nullopt when the name has no wallet.
void resolveDnsName(
const QString &name,
Fn<void(std::optional<QString>)> done,
Fn<void(DnsLookupError)> fail);
void estimateTonConnect(
uint64 owner,
std::shared_ptr<const TonConnectTransfer> transfer,
Fn<void(FeeResult)> done);
// |started| fires as the transfer leaves, with its chat message if any.
void send(
KeyAuthorization auth,
std::shared_ptr<const PreparedSend> prepared,
Fn<void(SendError)> done,
Fn<void(SendStarted)> started = nullptr);
[[nodiscard]] SendError sendRefusal(
const std::shared_ptr<const PreparedSend> &prepared,
const KeyAuthorization &auth);
void sendTonConnect(
KeyAuthorization auth,
std::shared_ptr<const PreparedSend> prepared,
TonConnectSendLink link,
Fn<void(TonConnectSendResult)> done,
Fn<void(SendError)> settled = nullptr);
[[nodiscard]] SendState sendState() const;
[[nodiscard]] rpl::producer<SendState> sendStateValue() const;
[[nodiscard]] std::optional<PendingSendInfo> pendingSend() const;
[[nodiscard]] auto lastTransferReceipt() const
-> const std::optional<TransferReceipt> &;
[[nodiscard]] std::vector<TransferItem> submittedTransactions() const;
[[nodiscard]] auto listedSubmittedTransactions() const
-> std::vector<ListedSubmittedTransfer>;
[[nodiscard]] std::optional<TransferItem> submittedTransaction(
const std::string &operationId) const;
// Includes the fallback record, wherever the history pages end.
[[nodiscard]] std::optional<TransferItem> trackedTransaction(
const std::string &operationId) const;
[[nodiscard]] std::optional<TransferItem> sendingTransaction(
const std::string &operationId) const;
// The send the wallet window started, which its list shows as sending.
void setWindowSend(std::string operationId);
[[nodiscard]] const std::string &windowSend() const;
[[nodiscard]] TonConnectSendFate tonConnectSendFate(
const std::string &operationId);
private:
void requestState(
Fn<void(const MTPWalletState &)> done = nullptr,
Fn<void()> fail = nullptr);
void requestGaslessInfo();
void retireGaslessRequest();
void resetGaslessInfo();
void applyGaslessInfo(GaslessInfo info, bool refreshed);
void applyGaslessTerms(GaslessTerms terms);
void requestExistingWaltBalance();
void syncParkedChecks(
const CustodyStore &store,
const QString &servedAddress);
void requestParkedFunds(const QString &address);
void finishParkedCheck(
const QString &address,
uint64 revision,
std::optional<Gram::AddressFunds> funds);
void requestParkedChecks(bool refresh);
void dropEmptyParked();
void publishParkedBalance();
[[nodiscard]] bool parkedHidden(
const CustodyRecord &record,
const QString &servedAddress) const;
void applyState(const MTPWalletState &state, bool pushed);
void setPresence(Presence presence);
void revealLocally(
KeyAuthorization auth,
const CustodyRecord &record,
Fn<void(std::vector<QString>)> done,
Fn<void(const QString &)> fail);
void revealFromShares(
KeyAuthorization auth,
std::optional<Core::CloudPasswordResult> password,
Fn<void(std::vector<QString>, CustodyOutcome outcome)> done,
Fn<void(const QString &)> fail,
std::shared_ptr<CommentScope> scope = nullptr,
Fn<void()> authorized = nullptr);
void fetchShareParts(
KeyAuthorization auth,
const QString &token,
std::vector<int> dcs,
Fn<void(std::vector<QString>, CustodyOutcome outcome)> done,
Fn<void(const QString &)> fail,
std::shared_ptr<CommentScope> scope,
mtpRequestId exportRequestId);
void restoreFromWords(
KeyAuthorization auth,
std::vector<QString> words,
Fn<void(std::vector<QString>, CustodyOutcome outcome)> done,
Fn<void(const QString &)> fail,
std::shared_ptr<CommentScope> scope = nullptr,
mtpRequestId exportRequestId = 0);
[[nodiscard]] QString phraseDiagnosticState() const;
[[nodiscard]] Fn<void(const QString &)> loggedPhraseFail(
const QString &stage,
Fn<void(const QString &)> fail);
// The phrase's anchor and current signing public keys, both derived
// storage- and network-free on the engine's local worker; nullopt for a
// phrase that is not a valid Rotation mnemonic. done runs on the main
// thread.
void validatePhraseIdentity(
const std::vector<QString> &words,
Fn<void(std::optional<PhraseIdentity>)> done);
[[nodiscard]] bool commentAccessAvailable() const;
// Runtime only, never written anywhere: a protected secret this device
// holds and could not read moves the device to its read-only view, so
// the restore and import ladder becomes reachable and a relaunch tries
// the secret again. The signal cannot be told apart from a keyring that
// is momentarily shut, so nothing irreversible follows from it; only a
// secret the host states is not there at all drops its record, which is
// the one answer that names no key any more.
[[nodiscard]] bool secretUnreadable(const QString &recordId) const;
void noteSecretReadFailure(
SecretReadFailure failure,
const QString &recordId);
// Drops the verdict once the record it names is no longer stored.
void validateUnreadableRecord();
void validateCommentScopes();
void retireCommentScopes(
const std::shared_ptr<CommentScope> &except = nullptr);
[[nodiscard]] const CustodyStore &custody();
[[nodiscard]] const CustodyRecord *currentRecord();
[[nodiscard]] bool persistCustody(const CustodyRecord &record);
void resetUnusableVault(
const QByteArray &expected,
const std::shared_ptr<CommentScope> &scope,
Fn<void(CustodyResetResult)> done);
void resetDeviceCustody(
const std::shared_ptr<CommentScope> &scope,
Fn<bool()> current,
Fn<void(CustodyResetResult)> done);
[[nodiscard]] CustodyInstallRequest resettableInstallRequest(
QByteArray expected,
std::shared_ptr<CommentScope> scope,
std::shared_ptr<bool> crossed,
Fn<void(CustodyInstall)> proceed);
void settleVaultReset(
const std::shared_ptr<bool> &crossed,
bool installed);
void sendReplaceWallet(
const MTPInputWalletReplacement &wallet,
std::optional<Core::CloudPasswordResult> password,
Fn<void(const MTPWalletState &)> applied,
Fn<void(const MTP::Error &)> fail);
void recoverImportedReplace(
QString canonicalAddress,
QByteArray signingKey,
Fn<void(const MTPWalletState &)> applied,
Fn<void(const QString &)> abandon);
struct OwnershipProof {
TimeId timestamp = 0;
std::vector<uint8_t> signature;
};
enum class OwnershipProofError {
Failed,
VaultLocked,
};
void requestOwnershipProof(
wallet_engine::WalletDescriptor descriptor,
QByteArray signingKey,
VaultAuthorization grant,
Fn<void(OwnershipProof)> done,
Fn<void(OwnershipProofError)> fail);
void settleRefusedBackupChange(
QString address,
QByteArray proofKey,
bool backupEnabled,
QString error,
Fn<void()> done,
Fn<void(const QString &)> fail);
[[nodiscard]] QString backupEnableRefusal(
const QString &address,
const QString &recordId,
const QByteArray &proofKey,
const QString &error);
void finishConfirmedReplace(
QString oldAddress,
std::optional<CustodyRecord> newActive,
const MTPWalletState &state,
Fn<void(CustodyOutcome)> done,
Fn<void(const QString &)> fail);
void reconcileCustody();
void updateDeviceCustodyState(bool cachedOnly = false);
void syncEngineClient();
void stopEngineClientForReset(Fn<void()> done);
// The live client only while it is bound to a custody record, so every
// journal, rotation and comment path that needs the secret sees no
// client at all under a public-key-only one.
[[nodiscard]] auto signingClient() const
-> std::shared_ptr<wallet_engine::WalletClient>;
void updateSigningReady();
// Decryptions that arrived while the public-key-only client was being
// swapped for the signing one run once that swap has settled.
struct DeferredDecrypt {
KeyAuthorization auth;
std::shared_ptr<CommentScope> scope;
Fn<void(CommentDecryptResult)> done;
int attempts = 0;
};
void decryptComment(DeferredDecrypt request);
void settleDeferredDecrypts();
void removeCustodyRecord(const QString &recordId);
[[nodiscard]] bool parked(const CustodyRecord &record) const;
void establishSigningKey(
const QString &recordId,
const PhraseIdentity &identity);
void clearNetworkState();
void pollTick();
void updatePollingState();
void applyStreamRefresh(StreamRefresh wanted);
void requestEngineRefresh();
void applyEngineUpdate(
const wallet_engine::WalletUpdate &update,
uint64 sendRevision);
void setHistory(std::vector<TransferItem> &&list);
[[nodiscard]] bool listRequestCurrent(
const std::optional<TransferWalletIdentity> &identity,
uint64 identityRevision,
int generation) const;
struct HistoryRequest;
void requestTransactions(bool more, Fn<void()> done = nullptr);
[[nodiscard]] bool historyRequestCurrent(
const HistoryRequest &request) const;
void applyTransactions(
const MTPwallet_Transactions &result,
bool more,
const HistoryRequest &request);
void refreshStaleHistory();
void applyTransferMinNanos();
void applyWalletAvailable();
void continueHiddenHistory(bool progressed);
void clearHistory();
void clearCollectibles();
void refreshCollectibles(bool force = false);
void requestCollectibles(bool more);
struct CollectiblesRequest;
void applyCollectibles(
const MTPwallet_NftItems &result,
const CollectiblesRequest &request);
void setCollectibles(std::vector<Gram::NftItem> &&list);
void rememberCollectibles(const std::vector<TransferItem> &items);
void followCollectibles(const std::vector<TransferItem> &arrived);
void followCollectible(const QString &address, bool incoming);
void spendCollectibleFollowUps(const std::vector<Gram::NftItem> *list);
struct PreviewRequest;
struct PreviewState;
[[nodiscard]] bool previewCurrent(const PreviewRequest &request) const;
[[nodiscard]] bool previewClientMatches(
const TransferWalletIdentity &identity,
const std::shared_ptr<wallet_engine::WalletClient> &client) const;
[[nodiscard]] bool transferClientMatches(
const TransferWalletIdentity &identity,
const std::shared_ptr<wallet_engine::WalletClient> &client) const;
[[nodiscard]] SendError previewError(const PreviewRequest &request);
void enqueuePreview(PreviewRequest request);
void startPreview();
void previewPrepared(uint64 flight, wallet_engine::SendMessageBody body);
void previewCollectible(uint64 flight);
void finishPreview(uint64 flight, FeeResult result);
void cancelPreview();
void finishPreviewCancel(uint64 flight);
void settlePreview();
void retirePreviewOwner(uint64 owner);
void retirePreviews(SendError error);
void resolveCommentRecipientAttempt(
const QString &destination,
bool bounce,
const QByteArray &recipientPublicKey,
Fn<void(CommentRecipient)> done,
int attempt);
[[nodiscard]] bool sendRecoveryNeeded() const;
void releaseDeferredRows();
void restoreSubmittedTransfers();
void dropForeignSubmittedTransfers(const TransferWalletIdentity &identity);
void expireStaleSubmittedTransfers();
void resolvePending();
void updateListsGate();
[[nodiscard]] bool listsConfirmedEmpty() const;
void finishPending();
void applySendSnapshot(
const wallet_engine::SendSnapshot &snapshot,
bool journalAuthoritative,
uint64 sendRevision);
void startSend(
KeyAuthorization auth,
std::shared_ptr<const PreparedSend> prepared,
Fn<void(SendError)> done,
Fn<void(SendStarted)> started,
Fn<void(TonConnectSendResult)> tonConnect,
TonConnectSendLink tonConnectLink);
[[nodiscard]] bool submissionCurrent(
const std::string &operationId,
const std::shared_ptr<const PreparedSend> &prepared) const;
void submitTransfer(
std::string operationId,
std::shared_ptr<const PreparedSend> prepared,
TransferSubmissionData data,
Fn<void(TransferSubmissionAnswer)> done);
void settleKeyMismatch(
TransferWalletIdentity identity,
QByteArray signingKey,
Fn<void(SendError)> done);
[[nodiscard]] bool bindTransferReceipt(
const std::string &operationId,
const std::shared_ptr<const PreparedSend> &prepared,
const MTPDupdateSentWalletTransaction &data);
[[nodiscard]] bool applySubmittedUpdate(
const std::string &operationId,
const MTPDupdateSentWalletTransaction &data);
[[nodiscard]] bool adoptSubmittedTransaction(
const std::string &operationId,
TransferItem item);
[[nodiscard]] bool transferOperationCurrent(
const TransferWalletIdentity &identity,
int generation,
const std::shared_ptr<wallet_engine::WalletClient> &client) const;
struct SubmittedTransfer;
struct SubmittedLookup;
[[nodiscard]] SubmittedTransferStore &submittedTransferStore();
[[nodiscard]] SubmittedTransferRecord *submittedTransferRecord(
const std::string &operationId,
const TransferWalletIdentity &identity);
[[nodiscard]] bool persistSubmittedTransfers();
void retireSubmittedTransferRecord(
const std::string &operationId,
const TransferWalletIdentity &identity);
[[nodiscard]] SubmittedTransfer *upsertSubmittedTransfer(
const std::string &operationId,
const TransferWalletIdentity &identity,
int generation,
const std::shared_ptr<wallet_engine::WalletClient> &client);
[[nodiscard]] SubmittedTransfer *submittedTransfer(
const std::string &operationId);
[[nodiscard]] const TransferItem *submittedShown(
const SubmittedTransfer &entry) const;
[[nodiscard]] bool submittedLookupNeeded() const;
[[nodiscard]] bool submittedLookupCurrent(
const std::shared_ptr<SubmittedLookup> &request) const;
void startSubmittedLookup();
void lookupSubmittedTransaction();
void applySubmittedLookup(
const MTPwallet_Transactions &result,
const std::shared_ptr<SubmittedLookup> &request);
void dropSubmittedIfListed();
void dropSubmittedLookup();
void retireSubmission();
void clearSubmittedTransfers();
void applyRotationSnapshot(
const wallet_engine::SendSnapshot &snapshot,
bool journalAuthoritative);
void storePendingRotation(
KeyAuthorization auth,
Fn<void()> done,
Fn<void(const QString &)> fail);
void discardPendingRotation();
void promotePendingRotation();
void finishRotation(const QString &error);
void clearRotatedSinceBackup();
const not_null<Main::Session*> _session;
Api _api;
MTP::Sender _stateApi;
const std::unique_ptr<Engine> _engine;
const std::unique_ptr<Rates> _rates;
const std::unique_ptr<Onramp> _onramp;
const std::unique_ptr<UserAddresses> _userAddresses;
const std::unique_ptr<TransferMessages> _transferMessages;
const std::unique_ptr<TonConnect> _tonConnect;
const std::unique_ptr<Stream> _stream;
base::Timer _pollTimer;
base::Timer _shareFetchTimer;
base::Timer _gaslessTimer;
base::Timer _decryptRetryTimer;
std::weak_ptr<ShareFetch> _shareFetch;
bool _loaded = false;
bool _walletAvailable = true;
QString _address;
QByteArray _publicKey;
QString _tonConnectOwnershipDomain;
uint64 _walletIdentityRevision = 0;
rpl::event_stream<> _transferWalletIdentityChanges;
rpl::variable<int64> _balanceNano = 0;
rpl::variable<Presence> _presence = Presence::Unknown;
rpl::variable<WalletCapabilities> _capabilities;
std::optional<CustodyStore> _custody;
bool _custodyReadFailed = false;
QString _unreadableRecordId;
bool _custodyResetting = false;
bool _phraseRevealing = false;
bool _replacing = false;
bool _backupChanging = false;
rpl::variable<DeviceCustodyState> _deviceCustody;
rpl::event_stream<> _custodyUpdates;
QString _clientRecordId;
std::optional<TransferWalletIdentity> _clientPreviewIdentity;
rpl::variable<bool> _signingReady = false;
bool _clientStopping = false;
AccountStatus _engineStatus = AccountStatus::NonExisting;
mtpRequestId _stateRequestId = 0;
crl::time _stateRequestedAt = 0;
crl::time _stateRefreshedAt = 0;
crl::time _engineRefreshedAt = 0;
int _stateFailures = 0;
// Set when the wallet cannot be read at all, from any of three sources:
// a wallet state that stayed unknown for kStateFailuresBeforeStated reads,
// a failed first transaction page of a wallet that does exist, or a wallet
// state whose address the parser refused. All three make the overview
// paint the unreachable face instead of the empty one.
bool _stateUnreachable = false;
int64 _transferMinNanos = kTransferMinNanosDefault;
rpl::variable<GaslessTerms> _gaslessTerms;
mtpRequestId _gaslessRequestId = 0;
uint64 _gaslessRequestSerial = 0;
crl::time _gaslessRequestedAt = 0;
crl::time _gaslessExpiresAt = 0;
bool _gaslessRefreshWanted = false;
bool _gaslessRefreshing = false;
rpl::variable<QString> _existingWaltBalanceUrl;
mtpRequestId _waltBalanceRequestId = 0;
bool _waltBalanceRequested = false;
// Checking and Empty hide a record: an empty unused one is dropped silently.
enum class ParkedFunds : uchar {
Checking,
Empty,
Funded,
Unknown,
};
struct ParkedCheck {
ParkedFunds funds = ParkedFunds::Checking;
int64 nano = 0;
uint64 revision = 0;
mtpRequestId requestId = 0;
};
base::flat_map<QString, ParkedCheck> _parkedChecks;
uint64 _parkedCheckRevision = 0;
bool _parkedDropping = false;
rpl::variable<std::optional<int64>> _parkedBalanceNano;
std::vector<TransferItem> _history;
rpl::event_stream<> _historyUpdates;
bool _historyHasNext = false;
// Set by new rows, cleared when paging stalls: a shown row stays shown.
std::optional<TimeId> _listedBoundary;
crl::time _historyRefreshedAt = 0;
std::vector<Gram::NftItem> _collectibles;
rpl::event_stream<> _collectiblesUpdates;
rpl::variable<bool> _collectiblesTab = false;
crl::time _collectiblesRefreshedAt = 0;
crl::time _collectiblesCompletedAt = 0;
std::shared_ptr<CollectiblesRequest> _collectiblesRequest;
bool _collectiblesHasMore = false;
QString _collectiblesNextOffset;
bool _collectiblesPaged = false;
bool _collectiblesForced = false;
base::flat_map<QString, Gram::NftItem> _collectibleInfo;
base::flat_map<
QString,
std::vector<Fn<void(const Gram::NftItem &)>>> _collectibleInfoWaiters;
struct CollectibleFollowUp {
bool incoming = false;
int left = 0;
};
base::flat_map<QString, CollectibleFollowUp> _collectibleFollowUps;
rpl::variable<bool> _listsGated = true;
rpl::event_stream<> _listsStateUpdates;
#ifdef _DEBUG
int _debugClearedPollingCount = 0;
#endif // _DEBUG
int _pollingCount = 0;
int _networkGeneration = 0;
std::shared_ptr<HistoryRequest> _historyRequest;
int _historyHiddenPages = 0;
bool _resolveRequestPending = false;
bool _sendRecoveryReady = false;
bool _engineRefreshPending = false;
bool _historySettled = false;
bool _historyUnreachable = false;
bool _historyPaged = false;
bool _historyStale = false;
crl::time _historyRequestedAt = 0;
QString _historyNextOffset;
rpl::variable<SendState> _sendState = SendState::Idle;
std::optional<PendingSendInfo> _pending;
bool _sendUnresolved = false;
std::string _unresolvedOperationId;
std::string _windowSend;
uint64 _sendRevision = 0;
struct TransferSubmissionState {
std::string operationId;
std::shared_ptr<const PreparedSend> prepared;
std::optional<TransferReceipt> receipt;
std::optional<SendError> refusal;
Fn<void(SendStarted)> started;
Fn<void(TonConnectSendResult)> tonConnect;
Fn<bool(const QString &)> tonConnectHandoff;
FullMsgId draft;
QByteArray normal;
TimeId posted = 0;
bool paired = false;
bool normalFeeAuthorized = false;
bool rpcStarted = false;
};
static void SettleTonConnect(
TransferSubmissionState &submission,
SendError error);
std::optional<TransferSubmissionState> _submission;
std::optional<SubmittedTransferStore> _submittedTransferStore;
bool _submittedTransfersDirty = false;
std::vector<SubmittedTransfer> _submitted;
std::shared_ptr<SubmittedLookup> _lookup;
std::string _lastLookupOperationId;
std::optional<TransferReceipt> _lastReceipt;
bool _previewPending = false;
std::unique_ptr<PreviewState> _preview;
struct PreparedRotation;
std::unique_ptr<PreparedRotation> _preparedRotation;
bool _rotating = false;
Fn<void()> _rotationConfirmed;
Fn<void(const QString &)> _rotationFailed;
std::vector<std::weak_ptr<CommentScope>> _commentScopes;
// Set only across the custody write of an install, so the signing-client
// swap that write causes keeps the scope the install was made for.
std::shared_ptr<CommentScope> _installingScope;
std::vector<DeferredDecrypt> _deferredDecrypts;
rpl::lifetime _commentLifetime;
std::unique_ptr<Ui::SeparatePanel> _panel;
rpl::lifetime _lifetime;
};
} // namespace Wallet