Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
08581d9
feat(psbt-draft-data): PSBT임시저장 관련 realm CRUD
pio-noncelab Nov 20, 2025
9bbe9bf
feat(transaction-draft-card): 임시 저장 트랜잭션 화면 및 카드 구현, RealmTransaction…
pio-noncelab Nov 20, 2025
a33475b
feat(transaction-draft-delete): 임시 저장 트랜잭션 삭제 기능
pio-noncelab Nov 20, 2025
9e0812c
feat(save-transaction-draft): 임시 저장 한 후에 '이동하기' 기능 구현
pio-noncelab Nov 20, 2025
99b95a2
feat(transaction-draft-save-diff): SelectedUtxos 비교 추가, UTXO 선택 화면에 S…
pio-noncelab Nov 20, 2025
21580a0
feat(transaction-draft-screen-order): 임시 저장 트랜잭션 화면에서 카드를 최신순으로 정렬, 지…
pio-noncelab Nov 20, 2025
30bf191
feat(transaction-draft-load): 임시저장 트랜잭션 불러오기, 이동하기 구현
pio-noncelab Nov 21, 2025
8cf3657
feat(signed-transaction-draft): 서명된 트랜잭션 임시 저장 기능, 불로오기 기능
pio-noncelab Nov 23, 2025
9a55c86
feat(delete-sent-transaction-draft): 전송 완료한 임시 저장 트랜잭션 삭제
pio-noncelab Nov 23, 2025
4b1ebda
fix(transaction-draft-sent): 임시저장 트랜잭션을 Load 후 삭제 로직 수정
pio-noncelab Nov 24, 2025
70b97ed
feat(large-font-ui): 큰 폰트 대응
pio-noncelab Nov 24, 2025
401e9eb
fix(sigend-transaction-draft): 서명 완료 드래프트는 SecureStorage에 저장하도록 변경
pio-noncelab Nov 24, 2025
bb78035
chore(cds): cds 브랜치 변경 (임시)
pio-noncelab Nov 24, 2025
744c05d
Merge branch 'develop' into feat/414-transaction-draft
ella-noncelab Jan 15, 2026
ea47c7d
fix: CDS languageCode 프로퍼티 추가
ella-noncelab Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions assets/i18n/en.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,39 @@ donation:
ln_address_pow: "[email protected]"
ln_invoice_api_error: "Failed to generate Lightning invoice."

transaction_draft:
title: "Temporary Transaction"
empty_message: "No temporary transaction saved"
deleted_wallet: "Deleted Wallet"
save: "Temporary Save"
load: "Load Transaction"
recipient_batch_address: "$address and $count other addresses"
fee_rate: "Fee Rate"
sats_per_vbyte: "sat/vB"
dialog:
transaction_draft_saved_send_screen: "Transaction Draft Saved"
transaction_draft_saved_send_screen_description: "The transaction information has been temporarily saved. Would you like to move to the list now?"
transaction_draft_saved_broadcast_screen: "Transaction Draft Saved"
transaction_draft_saved_description_broadcast_screen: "The transaction has been temporarily saved. Would you like to move to the list now?"
transaction_draft_save_failed: "Transaction Draft Save Failed"
transaction_draft_already_exists: "Transaction Draft Already Exists"
transaction_draft_empty_required_fields: "Please enter the address and amount to send."
transaction_unavailable_to_sign: "Transaction Unavailable to Sign"
transaction_already_used_utxo_included: "Transaction Already Used UTXO Included. Would you like to delete the saved transaction?"
transaction_has_been_locked_utxo_included: "Locked UTXO Included. Would you like to delete the saved transaction?"
transaction_draft_delete: "Delete Temporary Transaction"
transaction_draft_delete_description: "Would you like to delete the temporary transaction? Once deleted, it cannot be restored."
transaction_draft_delete_failed: "Transaction Draft Delete Failed"
transaction_draft_delete_completed: "Transaction Draft Delete Completed"
transaction_draft_delete_completed_description: "The temporary transaction has been deleted."
cancel: "Cancel"
remove: "Remove"
move: "Move"
confirm: "Confirm"
signed: "Signed"
unsigned: "Unsigned"
max: "Max"

# lib/enums
transaction_enums:
high_priority: "Fast"
Expand Down
33 changes: 33 additions & 0 deletions assets/i18n/jp.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,39 @@ donation:
ln_address_pow: "[email protected]"
ln_invoice_api_error: "ライトニングインボイスの生成に失敗しました。"

transaction_draft:
title: "一時保存トランザクション"
empty_message: "一時保存されたトランザクションがありません"
deleted_wallet: "削除されたウォレット"
save: "一時保存"
load: "読み込み"
recipient_batch_address: "$address および $count 個の他のアドレス"
fee_rate: "手数料率"
sats_per_vbyte: "sat/vB"
dialog:
transaction_draft_saved_send_screen: "送信情報保存完了"
transaction_draft_saved_send_screen_description: "後で署名できるように送信情報を一時保存しました。\n今すぐリストに移動しますか?"
transaction_draft_saved_broadcast_screen: "トランザクション保存完了"
transaction_draft_saved_description_broadcast_screen: "後で送信できるようにトランザクションを一時保存しました。\n今すぐリストに移動しますか?"
transaction_draft_save_failed: "トランザクション保存失敗"
transaction_draft_already_exists: "すでに保存されたトランザクションがあります。"
transaction_draft_empty_required_fields: "送信先と送信金額を両方入力してください。不足している項目を確認してください。"
transaction_unavailable_to_sign: "トランザクション署名不可"
transaction_already_used_utxo_included: "すでに使用されたUTXOが含まれています。保存されたトランザクションを削除しますか?"
transaction_has_been_locked_utxo_included: "ロックされたUTXOが含まれています。保存されたトランザクションを削除しますか?"
transaction_draft_delete: "一時保存トランザクション削除"
transaction_draft_delete_description: "一時保存されたトランザクションを削除しますか?一度削除すると元に戻すことはできません。"
transaction_draft_delete_failed: "一時保存トランザクション削除失敗"
transaction_draft_delete_completed: "一時保存トランザクション削除完了"
transaction_draft_delete_completed_description: "一時保存されたトランザクションを削除しました。"
cancel: "キャンセル"
remove: "削除"
move: "移動"
confirm: "確認"
signed: "署名完了"
unsigned: "署名未完了"
max: "最大"

# lib/enums
transaction_enums:
high_priority: "高速"
Expand Down
33 changes: 33 additions & 0 deletions assets/i18n/kr.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,39 @@ donation:
ln_address_pow: "[email protected]"
ln_invoice_api_error: "라이트닝 인보이스 생성에 실패했어요"

transaction_draft:
title: "임시 저장 트랜잭션"
empty_message: "임시 저장된 트랜잭션이 없어요"
deleted_wallet: "삭제된 지갑"
save: "임시 저장"
load: "불러오기"
recipient_batch_address: "$address 외 $count개 주소"
fee_rate: "수수료율"
sats_per_vbyte: "sat/vB"
dialog:
transaction_draft_saved_send_screen: "보내기 정보 저장 완료"
transaction_draft_saved_send_screen_description: "나중에 서명할 수 있도록 보내기 정보를 임시 저장했어요.\n지금 목록으로 이동할까요?"
transaction_draft_saved_broadcast_screen: "트랜잭션 저장 완료"
transaction_draft_saved_description_broadcast_screen: "나중에 전송할 수 있도록 트랜잭션을 임시 저장했어요.\n지금 목록으로 이동할까요?"
transaction_draft_save_failed: "트랜잭션 저장 실패"
transaction_draft_already_exists: "이미 저장된 트랜잭션이에요."
transaction_draft_empty_required_fields: "주소와 보낼 금액을 모두 입력해야 해요. 빠진 항목을 확인해 주세요."
transaction_unavailable_to_sign: "트랜잭션 서명 불가"
transaction_already_used_utxo_included: "이미 사용된 UTXO가 포함되어 있어요. 저장된 트랜잭션을 지울까요?"
transaction_has_been_locked_utxo_included: "잠금 설정이 된 UTXO가 포함되어 있어요. 저장된 트랜잭션을 지울까요?"
transaction_draft_delete: "임시 저장 트랜잭션 삭제"
transaction_draft_delete_description: "임시 저장된 트랜잭션을 삭제할까요? 한번 삭제하면 되돌릴 수 없어요."
transaction_draft_delete_failed: "임시 저장 트랜잭션 삭제 실패"
transaction_draft_delete_completed: "삭제 완료"
transaction_draft_delete_completed_description: "임시 저장 트랜잭션을 삭제했어요."
cancel: "취소"
remove: "지우기"
move: "이동하기"
confirm: "확인"
signed: "서명 완료"
unsigned: "서명 전"
max: "최대"

# lib/enums
transaction_enums:
high_priority: "빠른 전송"
Expand Down
28 changes: 24 additions & 4 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import 'package:coconut_wallet/providers/wallet_provider.dart';
import 'package:coconut_wallet/repository/realm/address_repository.dart';
import 'package:coconut_wallet/repository/realm/realm_manager.dart';
import 'package:coconut_wallet/repository/realm/subscription_repository.dart';
import 'package:coconut_wallet/repository/realm/model/coconut_wallet_model.dart';
import 'package:coconut_wallet/repository/realm/transaction_draft_repository.dart';
import 'package:coconut_wallet/repository/realm/transaction_repository.dart';
import 'package:coconut_wallet/repository/realm/utxo_repository.dart';
import 'package:coconut_wallet/providers/price_provider.dart';
Expand All @@ -31,6 +33,7 @@ import 'package:coconut_wallet/screens/settings/block_explorer_screen.dart';
import 'package:coconut_wallet/screens/settings/coconut_crew_screen.dart';
import 'package:coconut_wallet/screens/settings/electrum_server_screen.dart';
import 'package:coconut_wallet/screens/settings/log_viewer_screen.dart';
import 'package:coconut_wallet/screens/transaction_draft/transaction_draft_screen.dart';
import 'package:coconut_wallet/screens/wallet_detail/address_list_screen.dart';
import 'package:coconut_wallet/screens/review/negative_feedback_screen.dart';
import 'package:coconut_wallet/screens/review/positive_feedback_screen.dart';
Expand Down Expand Up @@ -114,6 +117,9 @@ class _CoconutWalletAppState extends State<CoconutWalletApp> {
Provider<AddressRepository>(create: (context) => AddressRepository(context.read<RealmManager>())),
Provider<TransactionRepository>(create: (context) => TransactionRepository(context.read<RealmManager>())),
Provider<UtxoRepository>(create: (context) => UtxoRepository(context.read<RealmManager>())),
Provider<TransactionDraftRepository>(
create: (context) => TransactionDraftRepository(context.read<RealmManager>(), context.read<UtxoRepository>()),
),
Provider<SubscriptionRepository>(create: (context) => SubscriptionRepository(context.read<RealmManager>())),
Provider<WalletPreferencesRepository>(
create: (context) => WalletPreferencesRepository(context.read<RealmManager>()),
Expand Down Expand Up @@ -237,7 +243,16 @@ class _CoconutWalletAppState extends State<CoconutWalletApp> {

// 로딩이 필요한 화면들
'/wallet-add-input': (context) => const CustomLoadingOverlay(child: WalletAddInputScreen()),
'/broadcasting': (context) => const CustomLoadingOverlay(child: BroadcastingScreen()),
'/broadcasting':
(context) => buildLoadingScreenWithArgs(
context,
(args) => BroadcastingScreen(
transactionDraft:
args.containsKey('transactionDraft')
? args['transactionDraft'] as RealmTransactionDraft?
: null,
),
),

// 인자가 있는 기본 화면들 (Privacy Screen 사용 ❌ - 각 화면 내부에서 설정/해제 합니다)
// 1. 주소 보기
Expand Down Expand Up @@ -285,7 +300,11 @@ class _CoconutWalletAppState extends State<CoconutWalletApp> {
'/send':
(context) => buildScreenWithArgs(
context,
(args) => SendScreen(walletId: args['walletId'], sendEntryPoint: args['sendEntryPoint']),
(args) => SendScreen(
walletId: args['walletId'],
sendEntryPoint: args['sendEntryPoint'],
transactionDraft: args['transactionDraft'],
),
),
'/utxo-tag': (context) => buildScreenWithArgs(context, (args) => UtxoTagCrudScreen(id: args['id'])),
'/select-donation-amount':
Expand Down Expand Up @@ -350,6 +369,7 @@ class _CoconutWalletAppState extends State<CoconutWalletApp> {
context,
(args) => UtxoDetailScreen(utxo: args['utxo'], id: args['id']),
),
'/transaction-draft': (context) => const TransactionDraftScreen(),
},
);

Expand All @@ -363,13 +383,13 @@ class _CoconutWalletAppState extends State<CoconutWalletApp> {
/// 화면 생성 헬퍼 메서드
/// 1. 인자가 있는 화면
Widget buildScreenWithArgs(BuildContext context, Widget Function(Map<String, dynamic>) builder) {
final Map<String, dynamic> args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>;
final Map<String, dynamic> args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>? ?? {};
return builder(args);
}

/// 2. CustomLoadingOverlay + 인자가 있는 화면
Widget buildLoadingScreenWithArgs(BuildContext context, Widget Function(Map<String, dynamic>) builder) {
final Map<String, dynamic> args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>;
final Map<String, dynamic> args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>? ?? {};
return CustomLoadingOverlay(child: builder(args));
}
}
2 changes: 1 addition & 1 deletion lib/constants/realm_constants.dart
Original file line number Diff line number Diff line change
@@ -1 +1 @@
const int kRealmVersion = 5;
const int kRealmVersion = 6;
1 change: 1 addition & 0 deletions lib/constants/secure_keys.dart
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
const kSecureStoragePinKey = 'pin';
const kSecureStorageSignedTransactionDraftPrefix = 'signed_transaction_draft_';
4 changes: 4 additions & 0 deletions lib/model/error/app_error.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ class ErrorCodes {
static AppError nodeIsolateError = AppError('1301', t.errors.node_unknown);
static AppError broadcastError = AppError('1302', t.errors.broadcast_error);
static AppError broadcastErrorWithMessage(String message) => ErrorCodes.withMessage(broadcastError, message);
static AppError transactionDraftAlreadyExists = AppError(
'1400',
t.transaction_draft.dialog.transaction_draft_already_exists,
);
}
16 changes: 15 additions & 1 deletion lib/providers/send_info_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class SendInfoProvider {
// null인 경우 RBF 또는 CPFP가 아닙니다.
FeeBumpingType? _feeBumpingType;
WalletImportSource? _walletImportSource;
int? _transactionDraftId;
double? _feeRate;

int? get walletId => _walletId;
String? get recipientAddress => _recipientAddress;
Expand All @@ -42,6 +44,16 @@ class SendInfoProvider {
_recipientsForBatch == null ? null : UnmodifiableMapView(_recipientsForBatch!);
FeeBumpingType? get feeBumpingType => _feeBumpingType;
WalletImportSource? get walletImportSource => _walletImportSource;
int? get transactionDraftId => _transactionDraftId;
double? get feeRate => _feeRate;

void setFeeRate(double feeRate) {
_feeRate = feeRate;
}

void setTransactionDraftId(int? id) {
_transactionDraftId = id;
}

void setWalletId(int id) {
_walletId = id;
Expand Down Expand Up @@ -116,7 +128,9 @@ class SendInfoProvider {
_rawSignedTransaction =
_isDonation =
_sendEntryPoint =
_recipientsForBatch = _feeBumpingType = _walletImportSource = null;
_recipientsForBatch =
_feeBumpingType =
_walletImportSource = _transactionDraftId = null;
}

Map<String, int>? getRecipientMap() {
Expand Down
19 changes: 18 additions & 1 deletion lib/providers/view_model/send/broadcasting_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dart:collection';
import 'dart:convert';

import 'package:coconut_lib/coconut_lib.dart';
import 'package:coconut_wallet/enums/network_enums.dart';
import 'package:coconut_wallet/localization/strings.g.dart';
import 'package:coconut_wallet/providers/node_provider/node_provider.dart';
import 'package:coconut_wallet/providers/send_info_provider.dart';
Expand Down Expand Up @@ -53,6 +52,15 @@ class BroadcastingViewModel extends ChangeNotifier {
_walletBase = _walletProvider.getWalletById(_sendInfoProvider.walletId!).walletBase;
_walletId = _sendInfoProvider.walletId!;
_isSendingDonation = _sendInfoProvider.isDonation ?? false;

debugPrint('sendInfoProvider.transactionDraftId: ${_sendInfoProvider.transactionDraftId}');
debugPrint('sendInfoProvider.signedPsbt: ${_sendInfoProvider.signedPsbt}');
debugPrint('sendInfoProvider.txWaitingForSign: ${_sendInfoProvider.txWaitingForSign}');
debugPrint('sendInfoProvider.transaction: ${_sendInfoProvider.transaction}');
debugPrint('sendInfoProvider.walletId: ${_sendInfoProvider.walletId}');
debugPrint('sendInfoProvider.walletBase: $_walletBase');
debugPrint('sendInfoProvider.walletAddressType: ${_walletBase.addressType}');
debugPrint('sendInfoProvider.walletId: $_walletId');
}

List<String> get recipientAddresses => UnmodifiableListView(_recipientAddresses);
Expand All @@ -74,6 +82,15 @@ class BroadcastingViewModel extends ChangeNotifier {

FeeBumpingType? get feeBumpingType => _sendInfoProvider.feeBumpingType;

int? get transactionDraftId => _sendInfoProvider.transactionDraftId;
double? get feeRate => _sendInfoProvider.feeRate;
bool? get isMaxMode => _sendInfoProvider.isMaxMode;
bool? get isMultisig => _sendInfoProvider.isMultisig;
Transaction? get transaction => _sendInfoProvider.transaction;
String? get txWaitingForSign => _sendInfoProvider.txWaitingForSign;
String? get signedPsbt => _sendInfoProvider.signedPsbt;
String? get signedPsbtBase64Encoded => _sendInfoProvider.signedPsbt;

Future<Result<String>> broadcast(Transaction signedTx) async {
Logger.log('BroadcastingViewModel: signedTx = ${signedTx.serialize()}');
final isConnected = await isElectrumServerConnected();
Expand Down
Loading