diff --git a/requirements.txt b/requirements.txt index 30e783de1aaf4cb29a207b3d495b61a39f0f9696..d7b272ee0da8195308e8af79fc8b5a85f4354588 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +pypeg2 aiohttp==3.6.2 async-timeout==3.0.1 asynctest==0.13.0 diff --git a/res/i18n/ts/cs.ts b/res/i18n/ts/cs.ts index 16b1f275f1bf081f6400d14f681290da5fa33176..562eb0b6bb055e3c99d679c06aa1b559d74295c7 100644 --- a/res/i18n/ts/cs.ts +++ b/res/i18n/ts/cs.ts @@ -78,7 +78,7 @@ <context> <name>CertificationView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="35"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> <source>&Ok</source> <translation type="unfinished">&Ok</translation> </message> @@ -260,67 +260,67 @@ <context> <name>ConnectionConfigController</name> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="211"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="212"/> <source>Broadcasting identity...</source> <translation type="unfinished">Diffusion de votre identité...</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="578"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="579"/> <source>connecting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="190"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="191"/> <source>Next</source> <translation type="unfinished">Suivant</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="197"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="198"/> <source> (Optional)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>Save a revocation document</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>All text files (*.txt)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="300"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="301"/> <source>Forbidden: pubkey is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="307"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="308"/> <source>Forbidden: pubkey is too long</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="315"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="316"/> <source>Error: passwords are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="323"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="324"/> <source>Error: salts are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="355"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="356"/> <source>Forbidden: salt is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="363"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="364"/> <source>Forbidden: password is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="395"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="396"/> <source>Revocation file</source> <translation type="unfinished"></translation> </message> @@ -575,105 +575,115 @@ p, li { white-space: pre-wrap; } <context> <name>ContextMenu</name> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="270"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="318"/> <source>Warning</source> <translation>Attention</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="41"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="43"/> <source>Informations</source> <translation type="unfinished">Informations</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="51"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="53"/> <source>Certify identity</source> <translation type="unfinished">Certifier cette identité</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="60"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="62"/> <source>View in Web of Trust</source> <translation type="unfinished">Voir dans la Toile de Confiance</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="186"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="234"/> <source>Send money</source> <translation type="unfinished">Envoyer de la monnaie</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="163"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="211"/> <source>Copy pubkey to clipboard</source> <translation type="unfinished">Copier la clé publique</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="172"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="220"/> <source>Copy pubkey to clipboard (with CRC)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="95"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="97"/> <source>Copy self-certification document to clipboard</source> <translation type="unfinished">Copier le document d'auto-certification</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="112"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="114"/> <source>Transfer</source> <translation type="unfinished">Transfert</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="116"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="118"/> <source>Send again</source> <translation type="unfinished">Renvoyer</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="125"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="127"/> <source>Cancel</source> <translation type="unfinished">Annuler</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="134"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="156"/> <source>Copy raw transaction to clipboard</source> <translation type="unfinished">Copier la transaction (format brut)</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="146"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="168"/> <source>Copy transaction block to clipboard</source> <translation type="unfinished">Copier le bloc de la transaction</translation> </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="198"/> + <source>Send as source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="187"/> + <source>Dividend</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>HistoryTableModel</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Date</source> <translation>Date</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Comment</source> <translation>Commentaire</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Amount</source> <translation type="unfinished">Montant</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Public key</source> <translation type="unfinished">Clé publique</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="185"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="201"/> <source>Transactions missing from history</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="476"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="501"/> <source>{0} / {1} confirmations</source> <translation type="unfinished">{0} / {1} confirmations</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="482"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="507"/> <source>Confirming... {0} %</source> <translation type="unfinished">Confirmation... {0} %</translation> </message> @@ -1302,7 +1312,7 @@ p, li { white-space: pre-wrap; } <context> <name>PasswordInputView</name> <message> - <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="33"/> + <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="37"/> <source>Password is valid</source> <translation type="unfinished"></translation> </message> @@ -1707,6 +1717,29 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> </context> +<context> + <name>SourcesServices</name> + <message> + <location filename="../../../src/sakia/services/sources.py" line="11"/> + <source>missing secret key for public key</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="14"/> + <source>missing password for hash</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="17"/> + <source>locked by a delay until</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="20"/> + <source>locked until</source> + <translation type="unfinished"></translation> + </message> +</context> <context> <name>StartupDialog</name> <message> @@ -1958,111 +1991,171 @@ please wait...</source> <context> <name>TransferController</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="141"/> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="167"/> <source>Transfer</source> <translation type="unfinished">Transfert</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="367"/> + <source>Check is successful!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="374"/> + <source><p><b>Condition</b></p>{}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="377"/> + <source><p><b>Errors</b><p></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="392"/> + <source><li>Error in {}: <span style="color: red">{} {}</span></li></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="399"/> + <source>Check source condition</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TransferMoneyWidget</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="154"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="270"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="156"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="275"/> <source>Transfer money to</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="157"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="276"/> <source>&Recipient public key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="158"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="277"/> <source>Key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="159"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="278"/> <source>Search &user</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="160"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="279"/> <source>Local ke&y</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="161"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="280"/> <source>Con&tact</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="162"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="281"/> <source>Available money: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="163"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="282"/> <source>Amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="164"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="283"/> <source> UD</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="165"/> - <source>Transaction message</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="288"/> + <source>Secret Key / Password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="166"/> - <source>Secret Key / Password</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="271"/> + <source>Select account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="155"/> - <source>Select account</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="284"/> + <source>Message</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="285"/> + <source>Spend condition</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="286"/> + <source>Receiver signature</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="287"/> + <source>Receiver signature or (sender after one week)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="272"/> + <source>Source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="273"/> + <source>Automatic</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="274"/> + <source>Check</source> <translation type="unfinished"></translation> </message> </context> <context> <name>TransferView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="29"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="30"/> <source>No amount. Please give the transfer amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="37"/> <source>Please enter correct password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="40"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="41"/> <source>Please enter a receiver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="44"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="45"/> <source>Incorrect receiver address or pubkey</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="222"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="229"/> <source>Transfer</source> <translation type="unfinished">Transfert</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="207"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="214"/> <source>Success sending money to {0}</source> <translation type="unfinished">Envoi de monnaie à {0} réussi</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="49"/> + <source>Source locked</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TxHistoryController</name> @@ -2093,33 +2186,38 @@ please wait...</source> <translation type="unfinished"></translation> </message> </context> -<context> +<context encoding="UTF-8"> <name>TxHistoryWidget</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="109"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="115"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="110"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="116"/> <source>Balance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="111"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="117"/> <source>loading...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="112"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="118"/> <source>Send money</source> <translation type="unfinished">Envoyer de la monnaie</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="114"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="120"/> <source>dd/MM/yyyy</source> <translation type="unfinished"></translation> </message> + <message encoding="UTF-8"> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="121"/> + <source><html><head/><body><p><span style=" color:#0000ff;">â– </span><span style=" font-style:italic;">to send </span><span style=" color:#ffb000;">â– </span><span style=" font-style:italic;">pending/not confirmed </span><span style=" color:#808080;">â– </span><span style=" font-style:italic;">refused </span><span style=" color:#000000;">â– </span> validated </p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>UserInformationView</name> diff --git a/res/i18n/ts/de.ts b/res/i18n/ts/de.ts index 771f256f62e672b28e8104f8f69b5f6f7afd1a8c..eab89cc57ebd43299e5fe5d5a1647e58f3788625 100644 --- a/res/i18n/ts/de.ts +++ b/res/i18n/ts/de.ts @@ -78,7 +78,7 @@ <context> <name>CertificationView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="35"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> <source>&Ok</source> <translation type="unfinished">&Ok</translation> </message> @@ -260,67 +260,67 @@ <context> <name>ConnectionConfigController</name> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="211"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="212"/> <source>Broadcasting identity...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="578"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="579"/> <source>connecting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="190"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="191"/> <source>Next</source> <translation type="unfinished">Weiter</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="197"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="198"/> <source> (Optional)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>Save a revocation document</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>All text files (*.txt)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="300"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="301"/> <source>Forbidden: pubkey is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="307"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="308"/> <source>Forbidden: pubkey is too long</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="315"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="316"/> <source>Error: passwords are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="323"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="324"/> <source>Error: salts are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="355"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="356"/> <source>Forbidden: salt is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="363"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="364"/> <source>Forbidden: password is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="395"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="396"/> <source>Revocation file</source> <translation type="unfinished"></translation> </message> @@ -575,105 +575,115 @@ p, li { white-space: pre-wrap; } <context> <name>ContextMenu</name> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="270"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="318"/> <source>Warning</source> <translation type="unfinished">Warnung</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="41"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="43"/> <source>Informations</source> <translation type="unfinished">Informationen</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="51"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="53"/> <source>Certify identity</source> <translation type="unfinished">Identität zertifizieren</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="60"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="62"/> <source>View in Web of Trust</source> <translation type="unfinished">Im Web of Trust anschauen</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="186"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="234"/> <source>Send money</source> <translation type="unfinished">Geld schicken</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="163"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="211"/> <source>Copy pubkey to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="172"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="220"/> <source>Copy pubkey to clipboard (with CRC)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="95"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="97"/> <source>Copy self-certification document to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="112"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="114"/> <source>Transfer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="116"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="118"/> <source>Send again</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="125"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="127"/> <source>Cancel</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="134"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="156"/> <source>Copy raw transaction to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="146"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="168"/> <source>Copy transaction block to clipboard</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="198"/> + <source>Send as source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="187"/> + <source>Dividend</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>HistoryTableModel</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Date</source> <translation>Datum</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Comment</source> <translation>Kommentar</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Public key</source> <translation type="unfinished">Einen öffentlichen Schlüssel</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="185"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="201"/> <source>Transactions missing from history</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="476"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="501"/> <source>{0} / {1} confirmations</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="482"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="507"/> <source>Confirming... {0} %</source> <translation type="unfinished"></translation> </message> @@ -1302,7 +1312,7 @@ p, li { white-space: pre-wrap; } <context> <name>PasswordInputView</name> <message> - <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="33"/> + <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="37"/> <source>Password is valid</source> <translation type="unfinished"></translation> </message> @@ -1707,6 +1717,29 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> </context> +<context> + <name>SourcesServices</name> + <message> + <location filename="../../../src/sakia/services/sources.py" line="11"/> + <source>missing secret key for public key</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="14"/> + <source>missing password for hash</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="17"/> + <source>locked by a delay until</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="20"/> + <source>locked until</source> + <translation type="unfinished"></translation> + </message> +</context> <context> <name>StartupDialog</name> <message> @@ -1958,111 +1991,171 @@ please wait...</source> <context> <name>TransferController</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="141"/> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="167"/> <source>Transfer</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="367"/> + <source>Check is successful!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="374"/> + <source><p><b>Condition</b></p>{}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="377"/> + <source><p><b>Errors</b><p></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="392"/> + <source><li>Error in {}: <span style="color: red">{} {}</span></li></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="399"/> + <source>Check source condition</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TransferMoneyWidget</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="154"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="270"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="156"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="275"/> <source>Transfer money to</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="157"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="276"/> <source>&Recipient public key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="158"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="277"/> <source>Key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="159"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="278"/> <source>Search &user</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="160"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="279"/> <source>Local ke&y</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="161"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="280"/> <source>Con&tact</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="162"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="281"/> <source>Available money: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="163"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="282"/> <source>Amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="164"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="283"/> <source> UD</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="165"/> - <source>Transaction message</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="288"/> + <source>Secret Key / Password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="166"/> - <source>Secret Key / Password</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="271"/> + <source>Select account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="155"/> - <source>Select account</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="284"/> + <source>Message</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="285"/> + <source>Spend condition</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="286"/> + <source>Receiver signature</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="287"/> + <source>Receiver signature or (sender after one week)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="272"/> + <source>Source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="273"/> + <source>Automatic</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="274"/> + <source>Check</source> <translation type="unfinished"></translation> </message> </context> <context> <name>TransferView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="29"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="30"/> <source>No amount. Please give the transfer amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="37"/> <source>Please enter correct password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="40"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="41"/> <source>Please enter a receiver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="44"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="45"/> <source>Incorrect receiver address or pubkey</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="222"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="229"/> <source>Transfer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="207"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="214"/> <source>Success sending money to {0}</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="49"/> + <source>Source locked</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TxHistoryController</name> @@ -2093,33 +2186,38 @@ please wait...</source> <translation type="unfinished"></translation> </message> </context> -<context> +<context encoding="UTF-8"> <name>TxHistoryWidget</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="109"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="115"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="110"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="116"/> <source>Balance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="111"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="117"/> <source>loading...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="112"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="118"/> <source>Send money</source> <translation type="unfinished">Geld schicken</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="114"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="120"/> <source>dd/MM/yyyy</source> <translation type="unfinished"></translation> </message> + <message encoding="UTF-8"> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="121"/> + <source><html><head/><body><p><span style=" color:#0000ff;">â– </span><span style=" font-style:italic;">to send </span><span style=" color:#ffb000;">â– </span><span style=" font-style:italic;">pending/not confirmed </span><span style=" color:#808080;">â– </span><span style=" font-style:italic;">refused </span><span style=" color:#000000;">â– </span> validated </p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>UserInformationView</name> diff --git a/res/i18n/ts/es.ts b/res/i18n/ts/es.ts index 36fdf2bb7f4b90c5d4ecd75a5e06ac496f2357f7..40611b6099d9c7f61d941800d1e973fad45762bf 100644 --- a/res/i18n/ts/es.ts +++ b/res/i18n/ts/es.ts @@ -78,7 +78,7 @@ <context> <name>CertificationView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="35"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> <source>&Ok</source> <translation type="unfinished"></translation> </message> @@ -260,67 +260,67 @@ <context> <name>ConnectionConfigController</name> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="211"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="212"/> <source>Broadcasting identity...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="578"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="579"/> <source>connecting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="190"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="191"/> <source>Next</source> <translation type="unfinished">Siguiente</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="197"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="198"/> <source> (Optional)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>Save a revocation document</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>All text files (*.txt)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="300"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="301"/> <source>Forbidden: pubkey is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="307"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="308"/> <source>Forbidden: pubkey is too long</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="315"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="316"/> <source>Error: passwords are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="323"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="324"/> <source>Error: salts are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="355"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="356"/> <source>Forbidden: salt is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="363"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="364"/> <source>Forbidden: password is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="395"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="396"/> <source>Revocation file</source> <translation type="unfinished"></translation> </message> @@ -575,105 +575,115 @@ p, li { white-space: pre-wrap; } <context> <name>ContextMenu</name> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="270"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="318"/> <source>Warning</source> <translation type="unfinished">Advertencia</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="41"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="43"/> <source>Informations</source> <translation type="unfinished">Informaciones</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="51"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="53"/> <source>Certify identity</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="60"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="62"/> <source>View in Web of Trust</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="186"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="234"/> <source>Send money</source> <translation type="unfinished">Enviar dinero</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="163"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="211"/> <source>Copy pubkey to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="172"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="220"/> <source>Copy pubkey to clipboard (with CRC)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="95"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="97"/> <source>Copy self-certification document to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="112"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="114"/> <source>Transfer</source> <translation type="unfinished">Transferir</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="116"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="118"/> <source>Send again</source> <translation type="unfinished">Enviar de nuevo</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="125"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="127"/> <source>Cancel</source> <translation type="unfinished">Cancelar</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="134"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="156"/> <source>Copy raw transaction to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="146"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="168"/> <source>Copy transaction block to clipboard</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="198"/> + <source>Send as source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="187"/> + <source>Dividend</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>HistoryTableModel</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Date</source> <translation>Fecha</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Comment</source> <translation type="unfinished">Comentario</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Amount</source> <translation type="unfinished">Cantidad</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Public key</source> <translation type="unfinished">Clave pública</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="185"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="201"/> <source>Transactions missing from history</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="476"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="501"/> <source>{0} / {1} confirmations</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="482"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="507"/> <source>Confirming... {0} %</source> <translation type="unfinished"></translation> </message> @@ -1302,7 +1312,7 @@ p, li { white-space: pre-wrap; } <context> <name>PasswordInputView</name> <message> - <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="33"/> + <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="37"/> <source>Password is valid</source> <translation type="unfinished"></translation> </message> @@ -1707,6 +1717,29 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> </context> +<context> + <name>SourcesServices</name> + <message> + <location filename="../../../src/sakia/services/sources.py" line="11"/> + <source>missing secret key for public key</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="14"/> + <source>missing password for hash</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="17"/> + <source>locked by a delay until</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="20"/> + <source>locked until</source> + <translation type="unfinished"></translation> + </message> +</context> <context> <name>StartupDialog</name> <message> @@ -1958,111 +1991,171 @@ please wait...</source> <context> <name>TransferController</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="141"/> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="167"/> <source>Transfer</source> <translation type="unfinished">Transferir</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="367"/> + <source>Check is successful!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="374"/> + <source><p><b>Condition</b></p>{}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="377"/> + <source><p><b>Errors</b><p></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="392"/> + <source><li>Error in {}: <span style="color: red">{} {}</span></li></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="399"/> + <source>Check source condition</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TransferMoneyWidget</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="154"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="270"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="156"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="275"/> <source>Transfer money to</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="157"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="276"/> <source>&Recipient public key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="158"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="277"/> <source>Key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="159"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="278"/> <source>Search &user</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="160"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="279"/> <source>Local ke&y</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="161"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="280"/> <source>Con&tact</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="162"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="281"/> <source>Available money: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="163"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="282"/> <source>Amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="164"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="283"/> <source> UD</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="165"/> - <source>Transaction message</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="288"/> + <source>Secret Key / Password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="166"/> - <source>Secret Key / Password</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="271"/> + <source>Select account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="155"/> - <source>Select account</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="284"/> + <source>Message</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="285"/> + <source>Spend condition</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="286"/> + <source>Receiver signature</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="287"/> + <source>Receiver signature or (sender after one week)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="272"/> + <source>Source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="273"/> + <source>Automatic</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="274"/> + <source>Check</source> <translation type="unfinished"></translation> </message> </context> <context> <name>TransferView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="29"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="30"/> <source>No amount. Please give the transfer amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="37"/> <source>Please enter correct password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="40"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="41"/> <source>Please enter a receiver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="44"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="45"/> <source>Incorrect receiver address or pubkey</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="222"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="229"/> <source>Transfer</source> <translation type="unfinished">Transferir</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="207"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="214"/> <source>Success sending money to {0}</source> <translation type="unfinished">Éxito enviar dinero a {0}</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="49"/> + <source>Source locked</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TxHistoryController</name> @@ -2093,33 +2186,38 @@ please wait...</source> <translation type="unfinished"></translation> </message> </context> -<context> +<context encoding="UTF-8"> <name>TxHistoryWidget</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="109"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="115"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="110"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="116"/> <source>Balance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="111"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="117"/> <source>loading...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="112"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="118"/> <source>Send money</source> <translation type="unfinished">Enviar dinero</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="114"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="120"/> <source>dd/MM/yyyy</source> <translation type="unfinished"></translation> </message> + <message encoding="UTF-8"> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="121"/> + <source><html><head/><body><p><span style=" color:#0000ff;">â– </span><span style=" font-style:italic;">to send </span><span style=" color:#ffb000;">â– </span><span style=" font-style:italic;">pending/not confirmed </span><span style=" color:#808080;">â– </span><span style=" font-style:italic;">refused </span><span style=" color:#000000;">â– </span> validated </p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>UserInformationView</name> diff --git a/res/i18n/ts/fr.ts b/res/i18n/ts/fr.ts index 160eabfdecdd6d3ee7a1424b815a7221a13e0d9e..fad51dfdf295b21073dfb92d875a8b8dfb870680 100644 --- a/res/i18n/ts/fr.ts +++ b/res/i18n/ts/fr.ts @@ -79,7 +79,7 @@ <context> <name>CertificationView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="35"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> <source>&Ok</source> <translation>&Ok</translation> </message> @@ -261,67 +261,67 @@ <context> <name>ConnectionConfigController</name> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="211"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="212"/> <source>Broadcasting identity...</source> <translation>Diffusion de votre identité...</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="578"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="579"/> <source>connecting...</source> <translation>connexion...</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="190"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="191"/> <source>Next</source> <translation>Suivant</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="197"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="198"/> <source> (Optional)</source> <translation> (Optionnel)</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>Save a revocation document</source> <translation>Enregistrer le document de révocation</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>All text files (*.txt)</source> <translation>Tous les fichiers txt (*.txt)</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="300"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="301"/> <source>Forbidden: pubkey is too short</source> <translation>Interdit : la clef publique est trop courte</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="307"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="308"/> <source>Forbidden: pubkey is too long</source> <translation>Interdit : la clef publique est trop longue</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="315"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="316"/> <source>Error: passwords are different</source> <translation>Erreur : mots de passe différents</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="323"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="324"/> <source>Error: salts are different</source> <translation>Erreur : les sels sont différents</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="355"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="356"/> <source>Forbidden: salt is too short</source> <translation>Interdit : le sel est trop court</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="363"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="364"/> <source>Forbidden: password is too short</source> <translation>Interdit : le mot de passe est trop court</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="395"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="396"/> <source>Revocation file</source> <translation>Fichier de révocation</translation> </message> @@ -576,105 +576,115 @@ p, li { white-space: pre-wrap; } <context> <name>ContextMenu</name> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="270"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="318"/> <source>Warning</source> <translation>Avertissement</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="41"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="43"/> <source>Informations</source> <translation>Informations</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="51"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="53"/> <source>Certify identity</source> <translation>Certifier cette identité</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="60"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="62"/> <source>View in Web of Trust</source> <translation>Voir dans la Toile de Confiance</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="186"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="234"/> <source>Send money</source> <translation>Envoyer de la monnaie</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="163"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="211"/> <source>Copy pubkey to clipboard</source> <translation>Copier la clé publique</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="172"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="220"/> <source>Copy pubkey to clipboard (with CRC)</source> <translation>Copier la clé publique (avec CRC)</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="95"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="97"/> <source>Copy self-certification document to clipboard</source> <translation>Copier le document d'auto-certification</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="112"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="114"/> <source>Transfer</source> <translation>Transfert</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="116"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="118"/> <source>Send again</source> <translation>Renvoyer</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="125"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="127"/> <source>Cancel</source> <translation>Annuler</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="134"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="156"/> <source>Copy raw transaction to clipboard</source> <translation>Copier la transaction (format brut)</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="146"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="168"/> <source>Copy transaction block to clipboard</source> <translation>Copier le bloc de la transaction</translation> </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="198"/> + <source>Send as source</source> + <translation>Envoyer comme source</translation> + </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="187"/> + <source>Dividend</source> + <translation>Dividende</translation> + </message> </context> <context> <name>HistoryTableModel</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Date</source> <translation>Date</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Comment</source> <translation>Commentaire</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Amount</source> <translation>Montant</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Public key</source> <translation>Clé publique</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="185"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="201"/> <source>Transactions missing from history</source> <translation>Transactions manquantes dans l'historique</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="476"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="501"/> <source>{0} / {1} confirmations</source> <translation>{0} / {1} confirmations</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="482"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="507"/> <source>Confirming... {0} %</source> <translation>Confirmation... {0} %</translation> </message> @@ -1303,7 +1313,7 @@ p, li { white-space: pre-wrap; } <context> <name>PasswordInputView</name> <message> - <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="33"/> + <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="37"/> <source>Password is valid</source> <translation>Mot de passe valide</translation> </message> @@ -1318,7 +1328,7 @@ p, li { white-space: pre-wrap; } <message> <location filename="../../../src/sakia/gui/sub/password_input/password_input_uic.py" line="36"/> <source>Please enter your secret key</source> - <translation>Veuillez entrer votre clé publique</translation> + <translation>Veuillez entrer votre clé secrète</translation> </message> </context> <context> @@ -1708,6 +1718,29 @@ p, li { white-space: pre-wrap; } <translation>Centrer la vue sur moi</translation> </message> </context> +<context> + <name>SourcesServices</name> + <message> + <location filename="../../../src/sakia/services/sources.py" line="11"/> + <source>missing secret key for public key</source> + <translation>clé secrète manquante pour clé publique</translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="14"/> + <source>missing password for hash</source> + <translation>mot de passe manquant pour hash</translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="17"/> + <source>locked by a delay until</source> + <translation>verrouillé par un délai jusqu'au</translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="20"/> + <source>locked until</source> + <translation>verrouillé jusqu'au</translation> + </message> +</context> <context> <name>StartupDialog</name> <message> @@ -1960,111 +1993,171 @@ veuillez patienter...</translation> <context> <name>TransferController</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="141"/> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="167"/> <source>Transfer</source> <translation>Transfert</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="367"/> + <source>Check is successful!</source> + <translation>Vérification réussie !</translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="374"/> + <source><p><b>Condition</b></p>{}</source> + <translation></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="377"/> + <source><p><b>Errors</b><p></source> + <translation><p><b>Erreurs</b><p></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="392"/> + <source><li>Error in {}: <span style="color: red">{} {}</span></li></source> + <translation><li>Erreur sur {}: <span style="color: red">{} {}</span></li></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="399"/> + <source>Check source condition</source> + <translation>Verification condition source</translation> + </message> </context> <context> <name>TransferMoneyWidget</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="154"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="270"/> <source>Form</source> <translation></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="156"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="275"/> <source>Transfer money to</source> <translation>Virement vers</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="157"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="276"/> <source>&Recipient public key</source> <translation>&Clé publique du destinataire</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="158"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="277"/> <source>Key</source> <translation>Clé</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="159"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="278"/> <source>Search &user</source> <translation>Rechercher &utilisateur</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="160"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="279"/> <source>Local ke&y</source> <translation>C&lé locale</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="161"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="280"/> <source>Con&tact</source> <translation></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="162"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="281"/> <source>Available money: </source> <translation>Monnaie disponible : </translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="163"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="282"/> <source>Amount</source> <translation>Montant</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="164"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="283"/> <source> UD</source> <translation> DU</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="165"/> - <source>Transaction message</source> - <translation>Message du virement</translation> - </message> - <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="166"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="288"/> <source>Secret Key / Password</source> <translation>Clé secrète / Mot de passe</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="155"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="271"/> <source>Select account</source> <translation>Sélectionnez un compte</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="284"/> + <source>Message</source> + <translation></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="285"/> + <source>Spend condition</source> + <translation>Condition de dépense</translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="286"/> + <source>Receiver signature</source> + <translation>Signature destinataire</translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="287"/> + <source>Receiver signature or (sender after one week)</source> + <translation>Signature destinataire ou (émetteur après une semaine)</translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="272"/> + <source>Source</source> + <translation></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="273"/> + <source>Automatic</source> + <translation>Automatique</translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="274"/> + <source>Check</source> + <translation>Vérifier</translation> + </message> </context> <context> <name>TransferView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="29"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="30"/> <source>No amount. Please give the transfer amount</source> <translation>Aucun montant. Veuillez donner un montant de transfert</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="37"/> <source>Please enter correct password</source> <translation>Veuillez entrer un mot de passe correct</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="40"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="41"/> <source>Please enter a receiver</source> <translation>Veuillez entrer un destinataire</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="44"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="45"/> <source>Incorrect receiver address or pubkey</source> <translation>Adresse ou clé publique du destinataire incorrecte</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="222"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="229"/> <source>Transfer</source> <translation>Transfert</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="207"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="214"/> <source>Success sending money to {0}</source> <translation>Envoi de monnaie à {0} réussi</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="49"/> + <source>Source locked</source> + <translation>Source verrouillée</translation> + </message> </context> <context> <name>TxHistoryController</name> @@ -2098,30 +2191,35 @@ veuillez patienter...</translation> <context> <name>TxHistoryWidget</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="109"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="115"/> <source>Form</source> <translation></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="110"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="116"/> <source>Balance</source> <translation>Solde</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="111"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="117"/> <source>loading...</source> <translation>chargement...</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="112"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="118"/> <source>Send money</source> <translation>Envoyer de la monnaie</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="114"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="120"/> <source>dd/MM/yyyy</source> <translation></translation> </message> + <message> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="121"/> + <source><html><head/><body><p><span style=" color:#0000ff;">â– </span><span style=" font-style:italic;">to send </span><span style=" color:#ffb000;">â– </span><span style=" font-style:italic;">pending/not confirmed </span><span style=" color:#808080;">â– </span><span style=" font-style:italic;">refused </span><span style=" color:#000000;">â– </span> validated </p></body></html></source> + <translation><html><head/><body><p><span style=" color:#0000ff;">â– </span><span style=" font-style:italic;">à envoyer </span><span style=" color:#ffb000;">â– </span><span style=" font-style:italic;">en attente/non confirmée </span><span style=" color:#808080;">â– </span><span style=" font-style:italic;">refusée </span><span style=" color:#000000;">â– </span> validée </p></body></html></translation> + </message> </context> <context> <name>UserInformationView</name> diff --git a/res/i18n/ts/it.ts b/res/i18n/ts/it.ts index 099e0eaca5a97d96690caede21c9a6670870d23f..6281933f1a1f63a96face053078d8cb69f90a132 100644 --- a/res/i18n/ts/it.ts +++ b/res/i18n/ts/it.ts @@ -78,7 +78,7 @@ <context> <name>CertificationView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="35"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> <source>&Ok</source> <translation type="unfinished"></translation> </message> @@ -260,67 +260,67 @@ <context> <name>ConnectionConfigController</name> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="211"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="212"/> <source>Broadcasting identity...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="578"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="579"/> <source>connecting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="190"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="191"/> <source>Next</source> <translation type="unfinished">Seguente</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="197"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="198"/> <source> (Optional)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>Save a revocation document</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>All text files (*.txt)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="300"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="301"/> <source>Forbidden: pubkey is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="307"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="308"/> <source>Forbidden: pubkey is too long</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="315"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="316"/> <source>Error: passwords are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="323"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="324"/> <source>Error: salts are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="355"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="356"/> <source>Forbidden: salt is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="363"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="364"/> <source>Forbidden: password is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="395"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="396"/> <source>Revocation file</source> <translation type="unfinished"></translation> </message> @@ -575,105 +575,115 @@ p, li { white-space: pre-wrap; } <context> <name>ContextMenu</name> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="270"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="318"/> <source>Warning</source> <translation type="unfinished">Avvertimento</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="41"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="43"/> <source>Informations</source> <translation type="unfinished">Informazioni</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="51"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="53"/> <source>Certify identity</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="60"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="62"/> <source>View in Web of Trust</source> <translation type="unfinished">Vedi in Rete della Fiducia</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="186"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="234"/> <source>Send money</source> <translation type="unfinished">Invia denaro</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="163"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="211"/> <source>Copy pubkey to clipboard</source> <translation type="unfinished">Copia chiave pubblica negli appunti</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="172"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="220"/> <source>Copy pubkey to clipboard (with CRC)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="95"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="97"/> <source>Copy self-certification document to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="112"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="114"/> <source>Transfer</source> <translation type="unfinished">Trasferi</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="116"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="118"/> <source>Send again</source> <translation type="unfinished">Invia di nuovo</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="125"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="127"/> <source>Cancel</source> <translation type="unfinished">Annulla</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="134"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="156"/> <source>Copy raw transaction to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="146"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="168"/> <source>Copy transaction block to clipboard</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="198"/> + <source>Send as source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="187"/> + <source>Dividend</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>HistoryTableModel</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Date</source> <translation>Data</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Comment</source> <translation>Commento</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Amount</source> <translation type="unfinished">Importo</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Public key</source> <translation type="unfinished">Chiave pubblica</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="185"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="201"/> <source>Transactions missing from history</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="476"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="501"/> <source>{0} / {1} confirmations</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="482"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="507"/> <source>Confirming... {0} %</source> <translation type="unfinished"></translation> </message> @@ -1302,7 +1312,7 @@ p, li { white-space: pre-wrap; } <context> <name>PasswordInputView</name> <message> - <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="33"/> + <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="37"/> <source>Password is valid</source> <translation type="unfinished"></translation> </message> @@ -1707,6 +1717,29 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> </context> +<context> + <name>SourcesServices</name> + <message> + <location filename="../../../src/sakia/services/sources.py" line="11"/> + <source>missing secret key for public key</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="14"/> + <source>missing password for hash</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="17"/> + <source>locked by a delay until</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="20"/> + <source>locked until</source> + <translation type="unfinished"></translation> + </message> +</context> <context> <name>StartupDialog</name> <message> @@ -1958,111 +1991,171 @@ please wait...</source> <context> <name>TransferController</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="141"/> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="167"/> <source>Transfer</source> <translation type="unfinished">Trasferi</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="367"/> + <source>Check is successful!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="374"/> + <source><p><b>Condition</b></p>{}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="377"/> + <source><p><b>Errors</b><p></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="392"/> + <source><li>Error in {}: <span style="color: red">{} {}</span></li></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="399"/> + <source>Check source condition</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TransferMoneyWidget</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="154"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="270"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="156"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="275"/> <source>Transfer money to</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="157"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="276"/> <source>&Recipient public key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="158"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="277"/> <source>Key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="159"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="278"/> <source>Search &user</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="160"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="279"/> <source>Local ke&y</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="161"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="280"/> <source>Con&tact</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="162"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="281"/> <source>Available money: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="163"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="282"/> <source>Amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="164"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="283"/> <source> UD</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="165"/> - <source>Transaction message</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="288"/> + <source>Secret Key / Password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="166"/> - <source>Secret Key / Password</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="271"/> + <source>Select account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="155"/> - <source>Select account</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="284"/> + <source>Message</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="285"/> + <source>Spend condition</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="286"/> + <source>Receiver signature</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="287"/> + <source>Receiver signature or (sender after one week)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="272"/> + <source>Source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="273"/> + <source>Automatic</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="274"/> + <source>Check</source> <translation type="unfinished"></translation> </message> </context> <context> <name>TransferView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="29"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="30"/> <source>No amount. Please give the transfer amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="37"/> <source>Please enter correct password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="40"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="41"/> <source>Please enter a receiver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="44"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="45"/> <source>Incorrect receiver address or pubkey</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="222"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="229"/> <source>Transfer</source> <translation type="unfinished">Trasferi</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="207"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="214"/> <source>Success sending money to {0}</source> <translation type="unfinished">Successo l'invio di denaro a {0}</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="49"/> + <source>Source locked</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TxHistoryController</name> @@ -2093,33 +2186,38 @@ please wait...</source> <translation type="unfinished"></translation> </message> </context> -<context> +<context encoding="UTF-8"> <name>TxHistoryWidget</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="109"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="115"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="110"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="116"/> <source>Balance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="111"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="117"/> <source>loading...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="112"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="118"/> <source>Send money</source> <translation type="unfinished">Invia denaro</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="114"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="120"/> <source>dd/MM/yyyy</source> <translation type="unfinished"></translation> </message> + <message encoding="UTF-8"> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="121"/> + <source><html><head/><body><p><span style=" color:#0000ff;">â– </span><span style=" font-style:italic;">to send </span><span style=" color:#ffb000;">â– </span><span style=" font-style:italic;">pending/not confirmed </span><span style=" color:#808080;">â– </span><span style=" font-style:italic;">refused </span><span style=" color:#000000;">â– </span> validated </p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>UserInformationView</name> diff --git a/res/i18n/ts/pl.ts b/res/i18n/ts/pl.ts index 4727bdad44fd96f003f3df1a1a286291f05e0716..a88be476c8ef739660ce0bad2522c06e336f2ab3 100644 --- a/res/i18n/ts/pl.ts +++ b/res/i18n/ts/pl.ts @@ -78,7 +78,7 @@ <context> <name>CertificationView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="35"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> <source>&Ok</source> <translation type="unfinished"></translation> </message> @@ -260,67 +260,67 @@ <context> <name>ConnectionConfigController</name> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="211"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="212"/> <source>Broadcasting identity...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="578"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="579"/> <source>connecting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="190"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="191"/> <source>Next</source> <translation type="unfinished">NastÄ™pny</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="197"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="198"/> <source> (Optional)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>Save a revocation document</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>All text files (*.txt)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="300"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="301"/> <source>Forbidden: pubkey is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="307"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="308"/> <source>Forbidden: pubkey is too long</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="315"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="316"/> <source>Error: passwords are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="323"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="324"/> <source>Error: salts are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="355"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="356"/> <source>Forbidden: salt is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="363"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="364"/> <source>Forbidden: password is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="395"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="396"/> <source>Revocation file</source> <translation type="unfinished"></translation> </message> @@ -575,105 +575,115 @@ p, li { white-space: pre-wrap; } <context> <name>ContextMenu</name> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="270"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="318"/> <source>Warning</source> <translation type="unfinished">Ostrzeżenie</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="41"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="43"/> <source>Informations</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="51"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="53"/> <source>Certify identity</source> <translation type="unfinished">PoÅ›wiadcza tożsamość</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="60"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="62"/> <source>View in Web of Trust</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="186"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="234"/> <source>Send money</source> <translation type="unfinished">WyÅ›lij pieniÄ…dze</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="163"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="211"/> <source>Copy pubkey to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="172"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="220"/> <source>Copy pubkey to clipboard (with CRC)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="95"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="97"/> <source>Copy self-certification document to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="112"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="114"/> <source>Transfer</source> <translation type="unfinished">Przenieść</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="116"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="118"/> <source>Send again</source> <translation type="unfinished">WyÅ›lij ponownie</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="125"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="127"/> <source>Cancel</source> <translation type="unfinished">Anuluj</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="134"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="156"/> <source>Copy raw transaction to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="146"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="168"/> <source>Copy transaction block to clipboard</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="198"/> + <source>Send as source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="187"/> + <source>Dividend</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>HistoryTableModel</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Date</source> <translation>Data</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Comment</source> <translation>Uwaga</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Amount</source> <translation type="unfinished">Ilość</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Public key</source> <translation type="unfinished">Klucz publiczny</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="185"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="201"/> <source>Transactions missing from history</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="476"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="501"/> <source>{0} / {1} confirmations</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="482"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="507"/> <source>Confirming... {0} %</source> <translation type="unfinished"></translation> </message> @@ -1302,7 +1312,7 @@ p, li { white-space: pre-wrap; } <context> <name>PasswordInputView</name> <message> - <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="33"/> + <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="37"/> <source>Password is valid</source> <translation type="unfinished"></translation> </message> @@ -1707,6 +1717,29 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> </context> +<context> + <name>SourcesServices</name> + <message> + <location filename="../../../src/sakia/services/sources.py" line="11"/> + <source>missing secret key for public key</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="14"/> + <source>missing password for hash</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="17"/> + <source>locked by a delay until</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="20"/> + <source>locked until</source> + <translation type="unfinished"></translation> + </message> +</context> <context> <name>StartupDialog</name> <message> @@ -1958,111 +1991,171 @@ please wait...</source> <context> <name>TransferController</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="141"/> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="167"/> <source>Transfer</source> <translation type="unfinished">Przenieść</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="367"/> + <source>Check is successful!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="374"/> + <source><p><b>Condition</b></p>{}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="377"/> + <source><p><b>Errors</b><p></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="392"/> + <source><li>Error in {}: <span style="color: red">{} {}</span></li></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="399"/> + <source>Check source condition</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TransferMoneyWidget</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="154"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="270"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="156"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="275"/> <source>Transfer money to</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="157"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="276"/> <source>&Recipient public key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="158"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="277"/> <source>Key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="159"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="278"/> <source>Search &user</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="160"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="279"/> <source>Local ke&y</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="161"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="280"/> <source>Con&tact</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="162"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="281"/> <source>Available money: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="163"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="282"/> <source>Amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="164"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="283"/> <source> UD</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="165"/> - <source>Transaction message</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="288"/> + <source>Secret Key / Password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="166"/> - <source>Secret Key / Password</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="271"/> + <source>Select account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="155"/> - <source>Select account</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="284"/> + <source>Message</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="285"/> + <source>Spend condition</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="286"/> + <source>Receiver signature</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="287"/> + <source>Receiver signature or (sender after one week)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="272"/> + <source>Source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="273"/> + <source>Automatic</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="274"/> + <source>Check</source> <translation type="unfinished"></translation> </message> </context> <context> <name>TransferView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="29"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="30"/> <source>No amount. Please give the transfer amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="37"/> <source>Please enter correct password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="40"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="41"/> <source>Please enter a receiver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="44"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="45"/> <source>Incorrect receiver address or pubkey</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="222"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="229"/> <source>Transfer</source> <translation type="unfinished">Przenieść</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="207"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="214"/> <source>Success sending money to {0}</source> <translation type="unfinished">Sukces wysyÅ‚ania pieniÄ™dzy do {0}</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="49"/> + <source>Source locked</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TxHistoryController</name> @@ -2093,33 +2186,38 @@ please wait...</source> <translation type="unfinished"></translation> </message> </context> -<context> +<context encoding="UTF-8"> <name>TxHistoryWidget</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="109"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="115"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="110"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="116"/> <source>Balance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="111"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="117"/> <source>loading...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="112"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="118"/> <source>Send money</source> <translation type="unfinished">WyÅ›lij pieniÄ…dze</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="114"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="120"/> <source>dd/MM/yyyy</source> <translation type="unfinished"></translation> </message> + <message encoding="UTF-8"> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="121"/> + <source><html><head/><body><p><span style=" color:#0000ff;">â– </span><span style=" font-style:italic;">to send </span><span style=" color:#ffb000;">â– </span><span style=" font-style:italic;">pending/not confirmed </span><span style=" color:#808080;">â– </span><span style=" font-style:italic;">refused </span><span style=" color:#000000;">â– </span> validated </p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>UserInformationView</name> diff --git a/res/i18n/ts/pt.ts b/res/i18n/ts/pt.ts index 86e956e6f33c6e511ecd1a84e66235760ffa2cd2..15994087756d05a226a1a48a4d10c61c7841cf24 100644 --- a/res/i18n/ts/pt.ts +++ b/res/i18n/ts/pt.ts @@ -78,7 +78,7 @@ <context> <name>CertificationView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="35"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> <source>&Ok</source> <translation type="unfinished"></translation> </message> @@ -260,67 +260,67 @@ <context> <name>ConnectionConfigController</name> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="211"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="212"/> <source>Broadcasting identity...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="578"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="579"/> <source>connecting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="190"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="191"/> <source>Next</source> <translation type="unfinished">Próximo</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="197"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="198"/> <source> (Optional)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>Save a revocation document</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>All text files (*.txt)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="300"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="301"/> <source>Forbidden: pubkey is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="307"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="308"/> <source>Forbidden: pubkey is too long</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="315"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="316"/> <source>Error: passwords are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="323"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="324"/> <source>Error: salts are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="355"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="356"/> <source>Forbidden: salt is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="363"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="364"/> <source>Forbidden: password is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="395"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="396"/> <source>Revocation file</source> <translation type="unfinished"></translation> </message> @@ -575,105 +575,115 @@ p, li { white-space: pre-wrap; } <context> <name>ContextMenu</name> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="270"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="318"/> <source>Warning</source> <translation type="unfinished">Aviso</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="41"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="43"/> <source>Informations</source> <translation type="unfinished">Informações</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="51"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="53"/> <source>Certify identity</source> <translation type="unfinished">Certificar identidade</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="60"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="62"/> <source>View in Web of Trust</source> <translation type="unfinished">Ver na Rede de Confiança</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="186"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="234"/> <source>Send money</source> <translation type="unfinished">Enviar dinheiro</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="163"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="211"/> <source>Copy pubkey to clipboard</source> <translation type="unfinished">Copiar chave pública para a área de transferência</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="172"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="220"/> <source>Copy pubkey to clipboard (with CRC)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="95"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="97"/> <source>Copy self-certification document to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="112"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="114"/> <source>Transfer</source> <translation type="unfinished">Transferência</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="116"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="118"/> <source>Send again</source> <translation type="unfinished">Enviar novamente</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="125"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="127"/> <source>Cancel</source> <translation type="unfinished">Cancelar</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="134"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="156"/> <source>Copy raw transaction to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="146"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="168"/> <source>Copy transaction block to clipboard</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="198"/> + <source>Send as source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="187"/> + <source>Dividend</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>HistoryTableModel</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Date</source> <translation>Data</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Comment</source> <translation>Comentário</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Amount</source> <translation type="unfinished">Quantia</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Public key</source> <translation type="unfinished">Chave pública</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="185"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="201"/> <source>Transactions missing from history</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="476"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="501"/> <source>{0} / {1} confirmations</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="482"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="507"/> <source>Confirming... {0} %</source> <translation type="unfinished"></translation> </message> @@ -1302,7 +1312,7 @@ p, li { white-space: pre-wrap; } <context> <name>PasswordInputView</name> <message> - <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="33"/> + <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="37"/> <source>Password is valid</source> <translation type="unfinished"></translation> </message> @@ -1707,6 +1717,29 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> </context> +<context> + <name>SourcesServices</name> + <message> + <location filename="../../../src/sakia/services/sources.py" line="11"/> + <source>missing secret key for public key</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="14"/> + <source>missing password for hash</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="17"/> + <source>locked by a delay until</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="20"/> + <source>locked until</source> + <translation type="unfinished"></translation> + </message> +</context> <context> <name>StartupDialog</name> <message> @@ -1958,111 +1991,171 @@ please wait...</source> <context> <name>TransferController</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="141"/> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="167"/> <source>Transfer</source> <translation type="unfinished">Transferência</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="367"/> + <source>Check is successful!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="374"/> + <source><p><b>Condition</b></p>{}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="377"/> + <source><p><b>Errors</b><p></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="392"/> + <source><li>Error in {}: <span style="color: red">{} {}</span></li></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="399"/> + <source>Check source condition</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TransferMoneyWidget</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="154"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="270"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="156"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="275"/> <source>Transfer money to</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="157"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="276"/> <source>&Recipient public key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="158"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="277"/> <source>Key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="159"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="278"/> <source>Search &user</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="160"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="279"/> <source>Local ke&y</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="161"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="280"/> <source>Con&tact</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="162"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="281"/> <source>Available money: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="163"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="282"/> <source>Amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="164"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="283"/> <source> UD</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="165"/> - <source>Transaction message</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="288"/> + <source>Secret Key / Password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="166"/> - <source>Secret Key / Password</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="271"/> + <source>Select account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="155"/> - <source>Select account</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="284"/> + <source>Message</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="285"/> + <source>Spend condition</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="286"/> + <source>Receiver signature</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="287"/> + <source>Receiver signature or (sender after one week)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="272"/> + <source>Source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="273"/> + <source>Automatic</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="274"/> + <source>Check</source> <translation type="unfinished"></translation> </message> </context> <context> <name>TransferView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="29"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="30"/> <source>No amount. Please give the transfer amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="37"/> <source>Please enter correct password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="40"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="41"/> <source>Please enter a receiver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="44"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="45"/> <source>Incorrect receiver address or pubkey</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="222"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="229"/> <source>Transfer</source> <translation type="unfinished">Transferência</translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="207"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="214"/> <source>Success sending money to {0}</source> <translation type="unfinished">Sucesso ao enviar dinheiro para {0}</translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="49"/> + <source>Source locked</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TxHistoryController</name> @@ -2093,33 +2186,38 @@ please wait...</source> <translation type="unfinished"></translation> </message> </context> -<context> +<context encoding="UTF-8"> <name>TxHistoryWidget</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="109"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="115"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="110"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="116"/> <source>Balance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="111"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="117"/> <source>loading...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="112"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="118"/> <source>Send money</source> <translation type="unfinished">Enviar dinheiro</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="114"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="120"/> <source>dd/MM/yyyy</source> <translation type="unfinished"></translation> </message> + <message encoding="UTF-8"> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="121"/> + <source><html><head/><body><p><span style=" color:#0000ff;">â– </span><span style=" font-style:italic;">to send </span><span style=" color:#ffb000;">â– </span><span style=" font-style:italic;">pending/not confirmed </span><span style=" color:#808080;">â– </span><span style=" font-style:italic;">refused </span><span style=" color:#000000;">â– </span> validated </p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>UserInformationView</name> diff --git a/res/i18n/ts/ru.ts b/res/i18n/ts/ru.ts index df8217981bf4c77140ef73a85fae7e395a8d5b64..66faa6df4fe9f4ce8da3156a819a9b8279482fcd 100644 --- a/res/i18n/ts/ru.ts +++ b/res/i18n/ts/ru.ts @@ -78,7 +78,7 @@ <context> <name>CertificationView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="35"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> <source>&Ok</source> <translation type="unfinished"></translation> </message> @@ -260,67 +260,67 @@ <context> <name>ConnectionConfigController</name> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="211"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="212"/> <source>Broadcasting identity...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="578"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="579"/> <source>connecting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="190"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="191"/> <source>Next</source> <translation type="unfinished">Следующий</translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="197"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="198"/> <source> (Optional)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>Save a revocation document</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="376"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="377"/> <source>All text files (*.txt)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="300"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="301"/> <source>Forbidden: pubkey is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="307"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="308"/> <source>Forbidden: pubkey is too long</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="315"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="316"/> <source>Error: passwords are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="323"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="324"/> <source>Error: salts are different</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="355"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="356"/> <source>Forbidden: salt is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="363"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="364"/> <source>Forbidden: password is too short</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="395"/> + <location filename="../../../src/sakia/gui/dialogs/connection_cfg/controller.py" line="396"/> <source>Revocation file</source> <translation type="unfinished"></translation> </message> @@ -575,105 +575,115 @@ p, li { white-space: pre-wrap; } <context> <name>ContextMenu</name> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="270"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="318"/> <source>Warning</source> <translation type="unfinished">Внимание</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="41"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="43"/> <source>Informations</source> <translation type="unfinished">Данные</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="51"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="53"/> <source>Certify identity</source> <translation type="unfinished">УдоÑтоверить личноÑть</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="60"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="62"/> <source>View in Web of Trust</source> <translation type="unfinished">ПоÑмотреть в Сети довериÑ</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="186"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="234"/> <source>Send money</source> <translation type="unfinished">Отправить деньги</translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="163"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="211"/> <source>Copy pubkey to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="172"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="220"/> <source>Copy pubkey to clipboard (with CRC)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="95"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="97"/> <source>Copy self-certification document to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="112"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="114"/> <source>Transfer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="116"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="118"/> <source>Send again</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="125"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="127"/> <source>Cancel</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="134"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="156"/> <source>Copy raw transaction to clipboard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="146"/> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="168"/> <source>Copy transaction block to clipboard</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="198"/> + <source>Send as source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/widgets/context_menu.py" line="187"/> + <source>Dividend</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>HistoryTableModel</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Date</source> <translation>Дата</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Comment</source> <translation>Комментарий</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="51"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="52"/> <source>Public key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="185"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="201"/> <source>Transactions missing from history</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="476"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="501"/> <source>{0} / {1} confirmations</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="482"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/table_model.py" line="507"/> <source>Confirming... {0} %</source> <translation type="unfinished"></translation> </message> @@ -1302,7 +1312,7 @@ p, li { white-space: pre-wrap; } <context> <name>PasswordInputView</name> <message> - <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="33"/> + <location filename="../../../src/sakia/gui/sub/password_input/view.py" line="37"/> <source>Password is valid</source> <translation type="unfinished"></translation> </message> @@ -1707,6 +1717,29 @@ p, li { white-space: pre-wrap; } <translation type="unfinished"></translation> </message> </context> +<context> + <name>SourcesServices</name> + <message> + <location filename="../../../src/sakia/services/sources.py" line="11"/> + <source>missing secret key for public key</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="14"/> + <source>missing password for hash</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="17"/> + <source>locked by a delay until</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/services/sources.py" line="20"/> + <source>locked until</source> + <translation type="unfinished"></translation> + </message> +</context> <context> <name>StartupDialog</name> <message> @@ -1958,111 +1991,171 @@ please wait...</source> <context> <name>TransferController</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="141"/> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="167"/> <source>Transfer</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="367"/> + <source>Check is successful!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="374"/> + <source><p><b>Condition</b></p>{}</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="377"/> + <source><p><b>Errors</b><p></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="392"/> + <source><li>Error in {}: <span style="color: red">{} {}</span></li></source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/controller.py" line="399"/> + <source>Check source condition</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TransferMoneyWidget</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="154"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="270"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="156"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="275"/> <source>Transfer money to</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="157"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="276"/> <source>&Recipient public key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="158"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="277"/> <source>Key</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="159"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="278"/> <source>Search &user</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="160"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="279"/> <source>Local ke&y</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="161"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="280"/> <source>Con&tact</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="162"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="281"/> <source>Available money: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="163"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="282"/> <source>Amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="164"/> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="283"/> <source> UD</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="165"/> - <source>Transaction message</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="288"/> + <source>Secret Key / Password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="166"/> - <source>Secret Key / Password</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="271"/> + <source>Select account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="155"/> - <source>Select account</source> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="284"/> + <source>Message</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="285"/> + <source>Spend condition</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="286"/> + <source>Receiver signature</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="287"/> + <source>Receiver signature or (sender after one week)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="272"/> + <source>Source</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="273"/> + <source>Automatic</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/transfer_uic.py" line="274"/> + <source>Check</source> <translation type="unfinished"></translation> </message> </context> <context> <name>TransferView</name> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="29"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="30"/> <source>No amount. Please give the transfer amount</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="36"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="37"/> <source>Please enter correct password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="40"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="41"/> <source>Please enter a receiver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="44"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="45"/> <source>Incorrect receiver address or pubkey</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="222"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="229"/> <source>Transfer</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="207"/> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="214"/> <source>Success sending money to {0}</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../../../src/sakia/gui/sub/transfer/view.py" line="49"/> + <source>Source locked</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>TxHistoryController</name> @@ -2093,33 +2186,38 @@ please wait...</source> <translation type="unfinished"></translation> </message> </context> -<context> +<context encoding="UTF-8"> <name>TxHistoryWidget</name> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="109"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="115"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="110"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="116"/> <source>Balance</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="111"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="117"/> <source>loading...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="112"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="118"/> <source>Send money</source> <translation type="unfinished">Отправить деньги</translation> </message> <message> - <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="114"/> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="120"/> <source>dd/MM/yyyy</source> <translation type="unfinished"></translation> </message> + <message encoding="UTF-8"> + <location filename="../../../src/sakia/gui/navigation/txhistory/txhistory_uic.py" line="121"/> + <source><html><head/><body><p><span style=" color:#0000ff;">â– </span><span style=" font-style:italic;">to send </span><span style=" color:#ffb000;">â– </span><span style=" font-style:italic;">pending/not confirmed </span><span style=" color:#808080;">â– </span><span style=" font-style:italic;">refused </span><span style=" color:#000000;">â– </span> validated </p></body></html></source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>UserInformationView</name> diff --git a/src/sakia/data/entities/source.py b/src/sakia/data/entities/source.py index f8b0cab1062e73a8e83a8ee80e50d5be7270a299..540bf7a5a74883e4f4f262c3008b9499d162e165 100644 --- a/src/sakia/data/entities/source.py +++ b/src/sakia/data/entities/source.py @@ -3,6 +3,10 @@ import attr @attr.s(hash=True) class Source: + + TYPE_TRANSACTION = "T" + TYPE_DIVIDEND = "D" + currency = attr.ib(converter=str) pubkey = attr.ib(converter=str) identifier = attr.ib(converter=str) @@ -10,3 +14,5 @@ class Source: type = attr.ib(converter=str, validator=lambda i, a, s: s == "T" or s == "D") amount = attr.ib(converter=int, hash=False) base = attr.ib(converter=int, hash=False) + conditions = attr.ib(converter=str, hash=False) + used_by = attr.ib(hash=False, default=None) diff --git a/src/sakia/data/entities/transaction.py b/src/sakia/data/entities/transaction.py index 82096a04466c1ac12440460043ecc617962545f3..460ca51bf8641d6c5a01892ff706c5d738a7ed37 100644 --- a/src/sakia/data/entities/transaction.py +++ b/src/sakia/data/entities/transaction.py @@ -111,18 +111,21 @@ class Transaction: Transaction entity :param str currency: the currency of the transaction + :param str pubkey: the pubkey of the issuer :param str sha_hash: the hash of the transaction :param int written_block: the number of the block :param duniterpy.documents.BlockUID blockstamp: the blockstamp of the transaction :param int timestamp: the timestamp of the transaction - :param str signature: the signature - :param str issuer: the pubkey of the issuer - :param str receiver: the pubkey of the receiver + :param str signatures: the signature + :param tuple issuers: tuple of pubkey of the issuers + :param tuple receivers: tuple of pubkey of the receivers :param int amount: the amount :param int amount_base: the amount base :param str comment: a comment - :param str txid: the transaction id to sort transctions + :param int txid: the transaction id to sort transctions :param int state: the state of the transaction + :param bool local: is the transaction local + :param str raw: the raw string of the transaction """ TO_SEND = 0 diff --git a/src/sakia/data/processors/dividends.py b/src/sakia/data/processors/dividends.py index 138914ecb4e770ecd96b78bb55f901121fa0e9a1..fdf16a5b0d0cf96db6fad13c6351188e041f19e3 100644 --- a/src/sakia/data/processors/dividends.py +++ b/src/sakia/data/processors/dividends.py @@ -1,6 +1,6 @@ import attr import logging -from ..entities import Dividend +from ..entities import Dividend, Source from .nodes import NodesProcessor from ..connectors import BmaConnector from duniterpy.api import bma @@ -82,7 +82,7 @@ class DividendsProcessor: txdoc = Transaction.from_signed_raw(tx.raw) for input in txdoc.inputs: if ( - input.source == "D" + input.source == Source.TYPE_DIVIDEND and input.origin_id == connection.pubkey and input.index not in block_numbers and input.index > start diff --git a/src/sakia/data/processors/sources.py b/src/sakia/data/processors/sources.py index 18e4a0bbc44bd269ae3a1a0779e017c286742aab..0ca1812d08af74071f34a51d2c787e129565efbb 100644 --- a/src/sakia/data/processors/sources.py +++ b/src/sakia/data/processors/sources.py @@ -35,6 +35,9 @@ class SourcesProcessor: except sqlite3.IntegrityError: self._logger.debug("Source already known: {0}".format(source.identifier)) + def get_one(self, **search): + return self._repo.get_one(**search) + def amount(self, currency, pubkey): """ Get the amount value of the sources for a given pubkey @@ -53,15 +56,27 @@ class SourcesProcessor: """ return self._repo.get_all(currency=currency, pubkey=pubkey) - def consume(self, sources): + def consume(self, sources, tx_sha_hash): """ + Consume sources in db by setting the used_by column to tx hash + + :param List(Source) sources: Source instances list + :param str tx_sha_hash: Hash of tx - :param currency: - :param sources: :return: """ for s in sources: - self._repo.drop(s) + self._repo.consume(s, tx_sha_hash) + + def restore_all(self, tx_sha_hash): + """ + Restore sources in db by setting the used_by column to null + + :param str tx_sha_hash: Hash of tx + + :return: + """ + self._repo.restore_all(tx_sha_hash) def insert(self, source): try: diff --git a/src/sakia/data/processors/transactions.py b/src/sakia/data/processors/transactions.py index 04ff3822e76f2bb36eaad9510b21d0b0bad9396d..6b4ce3f479f59fc0b4879498ad36637075546bc3 100644 --- a/src/sakia/data/processors/transactions.py +++ b/src/sakia/data/processors/transactions.py @@ -92,9 +92,12 @@ class TransactionsProcessor: except sqlite3.IntegrityError: self._repo.update(tx) - def find_by_hash(self, pubkey, sha_hash): + def find_by_pubkey_and_hash(self, pubkey: str, sha_hash: str) -> Transaction: return self._repo.get_one(pubkey=pubkey, sha_hash=sha_hash) + def find_one_by_hash(self, sha_hash: str) -> Transaction: + return self._repo.get_one(sha_hash=sha_hash) + def awaiting(self, currency): return self._repo.get_all(currency=currency, state=Transaction.AWAITING) diff --git a/src/sakia/data/repositories/006_add_sources_conditions_property.sql b/src/sakia/data/repositories/006_add_sources_conditions_property.sql new file mode 100644 index 0000000000000000000000000000000000000000..66b9f6ef7baa59c1ad2e3efd3f521ccbc59ce5e4 --- /dev/null +++ b/src/sakia/data/repositories/006_add_sources_conditions_property.sql @@ -0,0 +1,5 @@ +BEGIN TRANSACTION; + +ALTER TABLE sources ADD COLUMN conditions VARCHAR(255); + +COMMIT; diff --git a/src/sakia/data/repositories/007_add_sources_used_by_property.sql b/src/sakia/data/repositories/007_add_sources_used_by_property.sql new file mode 100644 index 0000000000000000000000000000000000000000..18c783a29ae9366d89cdb44e7d6b9b721a03182e --- /dev/null +++ b/src/sakia/data/repositories/007_add_sources_used_by_property.sql @@ -0,0 +1,5 @@ +BEGIN TRANSACTION; + +ALTER TABLE sources ADD COLUMN used_by VARCHAR(255) default null; + +COMMIT; diff --git a/src/sakia/data/repositories/meta.py b/src/sakia/data/repositories/meta.py index cee8a3f6ade50fd7f8f484370830e0125ba4ab0e..3f932843f681ca82cc2d275503645d5fab0f4ebc 100644 --- a/src/sakia/data/repositories/meta.py +++ b/src/sakia/data/repositories/meta.py @@ -7,7 +7,7 @@ from .connections import ConnectionsRepo from .identities import IdentitiesRepo from .blockchains import BlockchainsRepo from .certifications import CertificationsRepo -from .transactions import TransactionsRepo, Transaction +from .transactions import TransactionsRepo from .dividends import DividendsRepo from .nodes import NodesRepo, Node from .sources import SourcesRepo @@ -89,6 +89,8 @@ class SakiaDatabase: self.refactor_transactions, self.drop_incorrect_nodes, self.insert_last_mass_attribute, + self.add_sources_conditions_property, + self.add_sources_used_by_property, ] def upgrade_database(self, to=0): @@ -211,6 +213,28 @@ class SakiaDatabase: with self.conn: self.conn.executescript(sql_file.read()) + def add_sources_conditions_property(self): + self._logger.debug("Add sources conditions property") + sql_file = open( + os.path.join( + os.path.dirname(__file__), "006_add_sources_conditions_property.sql" + ), + "r", + ) + with self.conn: + self.conn.executescript(sql_file.read()) + + def add_sources_used_by_property(self): + self._logger.debug("Add sources used_by property") + sql_file = open( + os.path.join( + os.path.dirname(__file__), "007_add_sources_used_by_property.sql" + ), + "r", + ) + with self.conn: + self.conn.executescript(sql_file.read()) + def version(self): with self.conn: c = self.conn.execute("SELECT * FROM meta WHERE id=1") diff --git a/src/sakia/data/repositories/meta.sql b/src/sakia/data/repositories/meta.sql index 63785d61d3fe911ca9c72aec2b939fbba7c7d8e4..368007a00236877912362c6dfca11650a74c5a48 100644 --- a/src/sakia/data/repositories/meta.sql +++ b/src/sakia/data/repositories/meta.sql @@ -106,7 +106,7 @@ CREATE TABLE IF NOT EXISTS nodes( PRIMARY KEY (currency, pubkey) ); --- Cnnections TABLE +-- CONNECTIONS TABLE CREATE TABLE IF NOT EXISTS connections( currency VARCHAR(30), pubkey VARCHAR(50), @@ -118,7 +118,7 @@ CREATE TABLE IF NOT EXISTS connections( PRIMARY KEY (currency, pubkey) ); --- Cnnections TABLE +-- SOURCES TABLE CREATE TABLE IF NOT EXISTS sources( currency VARCHAR(30), pubkey VARCHAR(50), @@ -130,6 +130,7 @@ CREATE TABLE IF NOT EXISTS sources( PRIMARY KEY (currency, pubkey, identifier, noffset) ); +-- DIVIDENDS TABLE CREATE TABLE IF NOT EXISTS dividends( currency VARCHAR(30), pubkey VARCHAR(50), diff --git a/src/sakia/data/repositories/sources.py b/src/sakia/data/repositories/sources.py index 554c89d151b88db3e43c4759ebb14d8cafe99770..246777529e8723108401dd939027aa59bdd84b69 100644 --- a/src/sakia/data/repositories/sources.py +++ b/src/sakia/data/repositories/sources.py @@ -29,8 +29,9 @@ class SourcesRepo: def get_one(self, **search): """ - Get an existing source in the database - :param dict search: the criterions of the lookup + Get an existing not consumed source in the database + + :param ** search: the criterions of the lookup :rtype: sakia.data.entities.Source """ filters = [] @@ -39,7 +40,7 @@ class SourcesRepo: filters.append("{k}=?".format(k=k)) values.append(v) - request = "SELECT * FROM sources WHERE {filters}".format( + request = "SELECT * FROM sources WHERE used_by is NULL AND {filters}".format( filters=" AND ".join(filters) ) @@ -50,9 +51,10 @@ class SourcesRepo: def get_all(self, **search): """ - Get all existing source in the database corresponding to the search - :param dict search: the criterions of the lookup - :rtype: sakia.data.entities.Source + Get all existing not consumed source in the database corresponding to the search + + :param ** search: the criterions of the lookup + :rtype: [sakia.data.entities.Source] """ filters = [] values = [] @@ -61,7 +63,7 @@ class SourcesRepo: filters.append("{key} = ?".format(key=k)) values.append(value) - request = "SELECT * FROM sources WHERE {filters}".format( + request = "SELECT * FROM sources WHERE used_by is NULL AND {filters}".format( filters=" AND ".join(filters) ) @@ -101,3 +103,40 @@ class SourcesRepo: filters=" AND ".join(filters) ) self._conn.execute(request, tuple(values)) + + def consume(self, source, tx_hash): + """ + Consume a source by setting the used_by column with the tx hash + + :param Source source: Source instance to consume + :param str tx_hash: Hash of tx + :return: + """ + where_fields = attr.astuple( + source, filter=attr.filters.include(*SourcesRepo._primary_keys) + ) + fields = (tx_hash,) + where_fields + self._conn.execute( + """UPDATE sources SET used_by=? + WHERE + currency=? AND + pubkey=? AND + identifier=? AND + noffset=?""", + fields, + ) + + def restore_all(self, tx_hash): + """ + Restore all sources released by tx_hash setting the used_by column with null + + :param Source source: Source instance to consume + :param str tx_hash: Hash of tx + :return: + """ + self._conn.execute( + """UPDATE sources SET used_by=NULL + WHERE + used_by=?""", + (tx_hash,), + ) diff --git a/src/sakia/data/repositories/transactions.py b/src/sakia/data/repositories/transactions.py index 04ba645b34d3f201029be53a3bd296923c224d18..c51c8a5d35a3f3d07b2ec16e49ca1ba4d404cdbc 100644 --- a/src/sakia/data/repositories/transactions.py +++ b/src/sakia/data/repositories/transactions.py @@ -75,7 +75,7 @@ class TransactionsRepo: def get_one(self, **search): """ Get an existing transaction in the database - :param dict search: the criterions of the lookup + :param ** search: the criterions of the lookup :rtype: sakia.data.entities.Transaction """ filters = [] diff --git a/src/sakia/gui/navigation/txhistory/controller.py b/src/sakia/gui/navigation/txhistory/controller.py index f30362b0081129c8d62f9ef2616684b08d771782..2bbb639ee144d32862a5cda578e89d421e0dae66 100644 --- a/src/sakia/gui/navigation/txhistory/controller.py +++ b/src/sakia/gui/navigation/txhistory/controller.py @@ -55,8 +55,8 @@ class TxHistoryController(QObject): sources_service, ): - transfer = TransferController.integrate_to_main_view(None, app, connection) - view = TxHistoryView(parent.view, transfer.view) + controller = TransferController.integrate_to_main_view(None, app, connection) + view = TxHistoryView(parent.view, controller.view) model = TxHistoryModel( None, app, @@ -66,14 +66,14 @@ class TxHistoryController(QObject): transactions_service, sources_service, ) - txhistory = cls(view, model, transfer) + txhistory = cls(view, model, controller) model.setParent(txhistory) app.referential_changed.connect(txhistory.refresh_balance) app.sources_refreshed.connect(txhistory.refresh_balance) txhistory.view_in_wot.connect(app.view_in_wot) txhistory.view.spin_page.valueChanged.connect(model.change_page) - transfer.accepted.connect(view.clear) - transfer.rejected.connect(view.clear) + controller.accepted.connect(view.clear) + controller.rejected.connect(view.clear) return txhistory def refresh_minimum_maximum(self): diff --git a/src/sakia/gui/navigation/txhistory/sql_adapter.py b/src/sakia/gui/navigation/txhistory/sql_adapter.py index 294e39eca558660f3b312176eeb4997545a8fbbd..a00dbcbc81f524b8a732229448dce2a430b138d3 100644 --- a/src/sakia/gui/navigation/txhistory/sql_adapter.py +++ b/src/sakia/gui/navigation/txhistory/sql_adapter.py @@ -6,13 +6,16 @@ TX_HISTORY_REQUEST = """ SELECT transactions.ts, transactions.pubkey, - total_amount((amount * -1), amountbase) as amount, + total_amount((transactions.amount * -1), transactions.amountbase) as amount, transactions.comment , transactions.sha_hash, transactions.written_on, - transactions.txid + transactions.txid, + sources.conditions FROM transactions + LEFT JOIN sources ON sources.identifier = transactions.sha_hash and + (sources.conditions LIKE "%&&%" OR sources.conditions LIKE "%||%") WHERE transactions.currency = ? and transactions.pubkey = ? @@ -24,13 +27,16 @@ UNION ALL SELECT transactions.ts, transactions.pubkey, - total_amount(amount, amountbase) as amount, + total_amount(transactions.amount, transactions.amountbase) as amount, transactions.comment , transactions.sha_hash, transactions.written_on, - transactions.txid + transactions.txid, + sources.conditions FROM transactions + LEFT JOIN sources ON sources.identifier = transactions.sha_hash and + (sources.conditions LIKE "%&&%" OR sources.conditions LIKE "%||%") WHERE transactions.currency = ? and transactions.pubkey = ? @@ -42,11 +48,12 @@ UNION ALL SELECT dividends.timestamp as ts, dividends.pubkey , - total_amount(amount, base) as amount, + total_amount(dividends.amount, dividends.base) as amount, NULL as comment, NULL as sha_hash, dividends.block_number AS written_on, - 0 as txid + 0 as txid, + NULL FROM dividends WHERE diff --git a/src/sakia/gui/navigation/txhistory/table_model.py b/src/sakia/gui/navigation/txhistory/table_model.py index dae1eba1dcc152fcb379e50e06192dd17f10ad03..11c05b78a85555ae42d338d3056cf6ca0138c0fe 100644 --- a/src/sakia/gui/navigation/txhistory/table_model.py +++ b/src/sakia/gui/navigation/txhistory/table_model.py @@ -17,7 +17,7 @@ from sakia.data.entities import Transaction from sakia.constants import MAX_CONFIRMATIONS from sakia.data.processors import BlockchainProcessor from .sql_adapter import TxHistorySqlAdapter -from sakia.data.repositories import TransactionsRepo, DividendsRepo +from sakia.data.repositories import TransactionsRepo, DividendsRepo, SourcesRepo from sakia.data.entities.transaction import STOPLINE_HASH @@ -39,6 +39,7 @@ class HistoryTableModel(QAbstractTableModel): "block_number", "txhash", "raw_data", + "conditions", ) columns_to_sql = { @@ -81,6 +82,7 @@ class HistoryTableModel(QAbstractTableModel): self.sql_adapter = TxHistorySqlAdapter(self.app.db.conn) self.transactions_repo = TransactionsRepo(self.app.db.conn) self.dividends_repo = DividendsRepo(self.app.db.conn) + self.sources_repo = SourcesRepo(self.app.db.conn) self.current_page = 0 self.ts_from = ts_from self.ts_to = ts_to @@ -137,6 +139,20 @@ class HistoryTableModel(QAbstractTableModel): ) def change_transfer(self, transfer): + # capture sources associated with tx + sources = self.sources_repo.get_all( + currency=transfer.currency, identifier=transfer.sha_hash + ) + # capture complex conditions + sources_conditions = [ + source.conditions + for source in sources + if "%&&%" in source.conditions or "%||%" in source.conditions + ] + transfer.conditions = ( + sources_conditions[0] if len(sources_conditions) > 0 else None + ) + for i, data in enumerate(self.transfers_data): if ( data[HistoryTableModel.columns_types.index("txhash")] @@ -232,6 +248,7 @@ class HistoryTableModel(QAbstractTableModel): block_number, transfer.sha_hash, transfer, + transfer.conditions, ) def data_sent(self, transfer): @@ -267,6 +284,7 @@ class HistoryTableModel(QAbstractTableModel): block_number, transfer.sha_hash, transfer, + transfer.conditions, ) def data_dividend(self, dividend): @@ -299,6 +317,7 @@ class HistoryTableModel(QAbstractTableModel): block_number, "", dividend, + dividend.conditions, ) def init_transfers(self): @@ -312,7 +331,7 @@ class HistoryTableModel(QAbstractTableModel): pubkey=self.connection.pubkey, sha_hash=data[4], ) - + transfer.conditions = data[7] if transfer.state != Transaction.DROPPED: if data[4] == STOPLINE_HASH: self.transfers_data.append(self.data_stopline(transfer)) @@ -327,6 +346,7 @@ class HistoryTableModel(QAbstractTableModel): pubkey=self.connection.pubkey, block_number=data[5], ) + dividend.conditions = data[7] self.transfers_data.append(self.data_dividend(dividend)) self.endResetModel() @@ -334,7 +354,7 @@ class HistoryTableModel(QAbstractTableModel): return len(self.transfers_data) def columnCount(self, parent): - return len(HistoryTableModel.columns_types) - 6 + return 4 def sort(self, main_column, order): self.main_column_id = self.columns_types[main_column] @@ -374,7 +394,9 @@ class HistoryTableModel(QAbstractTableModel): block_data = self.transfers_data[row][ HistoryTableModel.columns_types.index("block_number") ] - + conditions_data = self.transfers_data[row][ + HistoryTableModel.columns_types.index("conditions") + ] if state_data == Transaction.VALIDATED and block_data: current_confirmations = ( self.blockchain_processor.current_buid(self.app.currency).number @@ -428,6 +450,9 @@ class HistoryTableModel(QAbstractTableModel): font.setItalic(True) else: font.setItalic(False) + # if special lock conditions on a source coming from this transaction... + if conditions_data is not None: + font.setUnderline(True) return font if role == Qt.ForegroundRole: diff --git a/src/sakia/gui/sub/password_input/view.py b/src/sakia/gui/sub/password_input/view.py index 97ba9bdd214bf45165740d27e5496712c4f8623c..0e016bd683ce17ebe2e3abe7a8b367f2ed553f8c 100644 --- a/src/sakia/gui/sub/password_input/view.py +++ b/src/sakia/gui/sub/password_input/view.py @@ -20,6 +20,10 @@ class PasswordInputView(QWidget, Ui_PasswordInputWidget): self.button_box.button(QDialogButtonBox.Ok).setEnabled(False) self.layout().addWidget(self.button_box) self.button_box.hide() + font = self.edit_secret_key.font() + font.setBold(False) + self.edit_secret_key.setFont(font) + self.edit_password.setFont(font) def error(self, text): self.label_info.setText(text) diff --git a/src/sakia/gui/sub/transfer/controller.py b/src/sakia/gui/sub/transfer/controller.py index 86408113dd88310e4024fd195e6871b985b382ac..25fb718a15b757f27043b7135465fb4663cd91b7 100644 --- a/src/sakia/gui/sub/transfer/controller.py +++ b/src/sakia/gui/sub/transfer/controller.py @@ -1,11 +1,14 @@ import re import logging -from PyQt5.QtCore import Qt, QObject, pyqtSignal, QCoreApplication -from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout +import pypeg2 +from PyQt5.QtCore import Qt, QObject, pyqtSignal, QCoreApplication, QLocale, QDateTime +from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout, QMessageBox from duniterpy.constants import PUBKEY_REGEX from duniterpy.documents import CRCPubkey +from duniterpy.grammars.output import Condition + from sakia.data.processors import ConnectionsProcessor from sakia.decorators import asyncify from sakia.gui.sub.password_input import PasswordInputController @@ -13,6 +16,7 @@ from sakia.gui.sub.search_user.controller import SearchUserController from sakia.gui.sub.user_information.controller import UserInformationController from sakia.money import Quantitative from sakia.gui.widgets.dialogs import dialog_async_exec +from sakia.services import sources from .model import TransferModel from .view import TransferView @@ -34,7 +38,7 @@ class TransferController(QObject): """ super().__init__() self.view = view - self.model = model + self.model = model # type: TransferModel self.search_user = search_user self.user_information = user_information self.password_input = password_input @@ -49,6 +53,7 @@ class TransferController(QObject): ) self.view.spinbox_amount.valueChanged.connect(self.handle_amount_change) self.view.spinbox_relative.valueChanged.connect(self.handle_relative_change) + self.view.button_source_check.clicked.connect(self.check_source_dialog) @classmethod def create(cls, parent, app): @@ -70,51 +75,73 @@ class TransferController(QObject): password_input.view, ) model = TransferModel(app) - transfer = cls(view, model, search_user, user_information, password_input) + controller = cls(view, model, search_user, user_information, password_input) search_user.identity_selected.connect(user_information.search_identity) - view.set_keys(transfer.model.available_connections()) - view.set_contacts(transfer.model.contacts()) + view.set_keys(controller.model.available_connections()) + view.set_contacts(controller.model.contacts()) app.new_connection.connect(view.add_key) app.connection_removed.connect(view.remove_key) - return transfer + return controller @classmethod def integrate_to_main_view(cls, parent, app, connection): - transfer = cls.create(parent, app) - transfer.view.combo_connections.setCurrentText(connection.title()) - transfer.view.radio_pubkey.toggle() - transfer.view.groupbox_connection.hide() - transfer.view.label_total.hide() - return transfer + controller = cls.create(parent, app) + controller.view.combo_connections.setCurrentText(connection.title()) + controller.view.radio_pubkey.toggle() + controller.view.widget_connections.hide() + controller.view.widget_source.hide() + controller.view.label_total.hide() + return controller @classmethod - def open_transfer_with_pubkey(cls, parent, app, connection, pubkey): - transfer = cls.create(parent, app) - transfer.view.groupbox_connection.show() + def open_transfer_with_pubkey(cls, parent, app, connection, pubkey, source): + controller = cls.create(parent, app) + controller.view.widget_connections.show() if connection: - transfer.view.combo_connections.setCurrentText(connection.title()) - transfer.view.edit_pubkey.setText(pubkey) - transfer.view.radio_pubkey.setChecked(True) - transfer.view.radio_pubkey.toggle() + controller.view.combo_connections.setCurrentText(connection.title()) + if pubkey: + controller.view.edit_pubkey.setText(pubkey) + controller.view.radio_pubkey.setChecked(True) + controller.view.radio_pubkey.toggle() + else: + controller.view.radio_local_key.setChecked(True) + controller.view.radio_local_key.toggle() + if source: + controller.model.current_source = source + controller.view.label_source_identifier.setText( + "{}:{}".format(source.identifier, source.noffset) + ) + controller.set_amount_value(source.amount, source.base) + controller.view.spinbox_amount.setDisabled(True) + controller.view.spinbox_relative.setDisabled(True) + result, _ = controller.check_source(source) + # by default, source is unlocked, if not... + if not result: + # enabled the check button to open the errors dialog + controller.view.button_source_check.setEnabled(True) + else: + controller.view.widget_source.hide() - transfer.refresh() - return transfer + controller.refresh() + return controller @classmethod - def send_money_to_pubkey(cls, parent, app, connection, pubkey): + def send_money_to_pubkey(cls, parent, app, connection, pubkey, source): dialog = QDialog(parent) dialog.setWindowTitle( QCoreApplication.translate("TransferController", "Transfer") ) dialog.setLayout(QVBoxLayout(dialog)) - transfer = cls.open_transfer_with_pubkey(parent, app, connection, pubkey) + controller = cls.open_transfer_with_pubkey( + parent, app, connection, pubkey, source + ) - dialog.layout().addWidget(transfer.view) - transfer.accepted.connect(dialog.accept) - transfer.rejected.connect(dialog.reject) + dialog.layout().addWidget(controller.view) + controller.accepted.connect(dialog.accept) + controller.rejected.connect(dialog.reject) return dialog.exec() @classmethod @@ -124,14 +151,14 @@ class TransferController(QObject): QCoreApplication.translate("TransferController", "Transfer") ) dialog.setLayout(QVBoxLayout(dialog)) - transfer = cls.open_transfer_with_pubkey( - parent, app, connection, identity.pubkey + controller = cls.open_transfer_with_pubkey( + parent, app, connection, identity.pubkey, None ) - transfer.user_information.change_identity(identity) - dialog.layout().addWidget(transfer.view) - transfer.accepted.connect(dialog.accept) - transfer.rejected.connect(dialog.reject) + controller.user_information.change_identity(identity) + dialog.layout().addWidget(controller.view) + controller.accepted.connect(dialog.accept) + controller.rejected.connect(dialog.reject) return dialog.exec() @classmethod @@ -141,34 +168,27 @@ class TransferController(QObject): QCoreApplication.translate("TransferController", "Transfer") ) dialog.setLayout(QVBoxLayout(dialog)) - transfer = cls.create(parent, app) - transfer.view.groupbox_connection.show() - transfer.view.label_total.show() - transfer.view.combo_connections.setCurrentText(connection.title()) - transfer.view.edit_pubkey.setText(resent_transfer.receivers[0]) - transfer.view.radio_pubkey.setChecked(True) - - transfer.refresh() - - current_base = transfer.model.current_base() - current_base_amount = resent_transfer.amount / pow( - 10, resent_transfer.amount_base - current_base - ) + controller = cls.create(parent, app) + controller.view.widget_connections.show() + controller.view.label_total.show() + controller.view.combo_connections.setCurrentText(connection.title()) + controller.view.edit_pubkey.setText(resent_transfer.receivers[0]) + controller.view.radio_pubkey.setChecked(True) + + controller.refresh() - relative = transfer.model.quant_to_rel(current_base_amount / 100) - transfer.view.set_spinboxes_parameters(current_base_amount / 100, relative) - transfer.view.change_relative_amount(relative) - transfer.view.change_quantitative_amount(current_base_amount / 100) + # display transaction amount + controller.set_amount_value(resent_transfer.amount, resent_transfer.amount_base) connections_processor = ConnectionsProcessor.instanciate(app) wallet_index = connections_processor.connections().index(connection) - transfer.view.combo_connections.setCurrentIndex(wallet_index) - transfer.view.edit_pubkey.setText(resent_transfer.receivers[0]) - transfer.view.radio_pubkey.toggle() - transfer.view.edit_message.setText(resent_transfer.comment) - dialog.layout().addWidget(transfer.view) - transfer.accepted.connect(dialog.accept) - transfer.rejected.connect(dialog.reject) + controller.view.combo_connections.setCurrentIndex(wallet_index) + controller.view.edit_pubkey.setText(resent_transfer.receivers[0]) + controller.view.radio_pubkey.toggle() + controller.view.edit_message.setText(resent_transfer.comment) + dialog.layout().addWidget(controller.view) + controller.accepted.connect(dialog.accept) + controller.rejected.connect(dialog.reject) return dialog.exec() def valid_crc_pubkey(self): @@ -215,7 +235,8 @@ class TransferController(QObject): async def accept(self): logging.debug("Accept transfer action...") self.view.button_box.setEnabled(False) - comment = self.view.edit_message.text() + + source = self.model.current_source logging.debug("checking recipient mode...") recipient = self.selected_pubkey() @@ -228,9 +249,19 @@ class TransferController(QObject): logging.debug("Setting cursor...") QApplication.setOverrideCursor(Qt.WaitCursor) + comment = self.view.edit_message.text() + lock_mode = self.view.combo_locks.currentIndex() + logging.debug("Send money...") result, transactions = await self.model.send_money( - recipient, secret_key, password, amount, amount_base, comment + recipient, + secret_key, + password, + amount, + amount_base, + comment, + lock_mode, + source, ) if result[0]: await self.view.show_success(self.model.notifications(), recipient) @@ -271,9 +302,13 @@ class TransferController(QObject): else: self.view.set_button_box(TransferView.ButtonBoxState.NO_RECEIVER) elif self.password_input.valid(): + # if source and check source successful... self.view.set_button_box(TransferView.ButtonBoxState.OK) else: self.view.set_button_box(TransferView.ButtonBoxState.WRONG_PASSWORD) + # if source and check source button still enabled... + if self.model.current_source and self.view.button_source_check.isEnabled(): + self.view.set_button_box(TransferView.ButtonBoxState.SOURCE_LOCKED) max_relative = self.model.quant_to_rel(current_base_amount / 100) self.view.spinbox_amount.setSuffix(Quantitative.base_str(current_base)) @@ -294,3 +329,95 @@ class TransferController(QObject): self.model.set_connection(index) self.password_input.set_connection(self.model.connection) self.refresh() + + def check_source(self, source): + """ + Check source conditions lock status + + Return a tupple with : + result: bool, + errors: list of tuples + [(condition: str, message: str, info: int|str),...] + + :param source: + :return tuple: + """ + # evaluate condition + condition = pypeg2.parse(source.conditions, Condition) + result, _errors = self.model.app.sources_service.evaluate_condition( + self.model.app.currency, + condition, + [self.model.connection.pubkey], + [], + source.identifier, + ) + return result, _errors + + def check_source_dialog(self): + """ + Open check source result and errors dialog + + :return: + """ + source = self.model.current_source + result, _errors = self.check_source(source) + + # if success... + if result: + message = QCoreApplication.translate( + "TransferController", "Check is successful!" + ) + self.view.button_source_check.setDisabled(True) + self.refresh() + # if failure... + else: + message = QCoreApplication.translate( + "TransferController", "<p><b>Condition</b></p>{}" + ).format(source.conditions) + message += QCoreApplication.translate( + "TransferController", "<p><b>Errors</b><p>" + ) + message += "<ul>" + # add error messages + for (_condition, _error, _param) in _errors: + if isinstance(_param, int): + _param = ( + QLocale.toString( + QLocale(), + QDateTime.fromTime_t(_param), + QLocale.dateTimeFormat(QLocale(), QLocale.ShortFormat), + ) + + " BAT" + ) + message += QCoreApplication.translate( + "TransferController", + '<li>Error in {}: <span style="color: red">{} {}</span></li>', + ).format( + _condition, + QCoreApplication.translate("SourcesServices", _error), + _param, + ) + message += "</ul>" + # open message box displaying source check result + qmessagebox = QMessageBox(self.view) + qmessagebox.setWindowTitle( + QCoreApplication.translate("TransferController", "Check source condition") + ) + qmessagebox.setText(message) + qmessagebox.exec() + + def set_amount_value(self, amount, base): + """ + Set quantitative and relative amounts from amount and base given + :param int amount: Amount to display + :param int base: Base of the amount given + :return: + """ + # calculate amount for current base + current_base = self.model.current_base() + current_base_amount = amount / pow(10, base - current_base) + # display quantitative and relative amounts + relative = self.model.quant_to_rel(current_base_amount / 100) + self.view.set_spinboxes_parameters(current_base_amount / 100, relative) + self.view.change_relative_amount(relative) + self.view.change_quantitative_amount(current_base_amount / 100) diff --git a/src/sakia/gui/sub/transfer/model.py b/src/sakia/gui/sub/transfer/model.py index d7d873068afe0609ad57666f04d2ccc8ec31b654..7b2c632db1c5ffa2b6ed9a80c6b86c8bdeddcd7a 100644 --- a/src/sakia/gui/sub/transfer/model.py +++ b/src/sakia/gui/sub/transfer/model.py @@ -21,6 +21,7 @@ class TransferModel(QObject): app = attr.ib() connection = attr.ib(default=None) resent_transfer = attr.ib(default=None) + current_source = attr.ib(default=None) _blockchain_processor = attr.ib(default=None) _sources_processor = attr.ib(default=None) _connections_processor = attr.ib(default=None) @@ -90,15 +91,27 @@ class TransferModel(QObject): self.connection = connections[index] async def send_money( - self, recipient, secret_key, password, amount, amount_base, comment + self, + recipient, + secret_key, + password, + amount, + amount_base, + comment, + lock_mode, + source, ): """ Send money to given recipient using the account + :param lock_mode: :param str recipient: + :param str password: + :param str secret_key: :param int amount: :param int amount_base: :param str comment: - :param str password: + :param int lock_mode: + :param Source source: :return: the result of the send """ @@ -110,6 +123,8 @@ class TransferModel(QObject): amount, amount_base, comment, + lock_mode, + source, ) for transaction in transactions: self.app.sources_service.parse_transaction_outputs( @@ -117,7 +132,7 @@ class TransferModel(QObject): ) for conn in self._connections_processor.connections(): if conn.pubkey == recipient: - self.app.sources_service.parse_transaction_inputs( + self.app.sources_service.consume_sources_from_transaction_inputs( recipient, transaction ) new_tx = self.app.transactions_service.parse_sent_transaction( diff --git a/src/sakia/gui/sub/transfer/transfer.ui b/src/sakia/gui/sub/transfer/transfer.ui index 10d81e625e3bfde45b853b8c0181743567bf1373..15341145e1873b68653bfa27fb1c46bcbff8402f 100644 --- a/src/sakia/gui/sub/transfer/transfer.ui +++ b/src/sakia/gui/sub/transfer/transfer.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>479</width> - <height>511</height> + <width>800</width> + <height>518</height> </rect> </property> <property name="windowTitle"> @@ -15,22 +15,102 @@ </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="QGroupBox" name="groupbox_connection"> - <property name="title"> - <string>Select account</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_4"> + <widget class="QWidget" name="widget_connections" native="true"> + <layout class="QHBoxLayout" name="layout_connections"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> <item> - <widget class="QComboBox" name="combo_connections"/> + <widget class="QLabel" name="label_connections"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Select account</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="combo_connections"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="widget_source" native="true"> + <layout class="QHBoxLayout" name="source_layout"> + <property name="spacing"> + <number>6</number> + </property> + <item> + <widget class="QLabel" name="label_source"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Source</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_source_identifier"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Automatic</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="button_source_check"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Check</string> + </property> + </widget> </item> </layout> </widget> </item> <item> <widget class="QGroupBox" name="group_box_recipient"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> <property name="title"> <string>Transfer money to</string> </property> + <property name="flat"> + <bool>false</bool> + </property> <layout class="QHBoxLayout" name="horizontalLayout_5"> <item> <layout class="QVBoxLayout" name="verticalLayout_4"> @@ -50,6 +130,12 @@ <verstretch>0</verstretch> </sizepolicy> </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> <property name="text"> <string>&Recipient public key</string> </property> @@ -85,6 +171,12 @@ <verstretch>0</verstretch> </sizepolicy> </property> + <property name="font"> + <font> + <weight>50</weight> + <bold>false</bold> + </font> + </property> <property name="inputMask"> <string/> </property> @@ -147,7 +239,14 @@ </widget> </item> <item> - <widget class="QComboBox" name="combo_local_keys"/> + <widget class="QComboBox" name="combo_local_keys"> + <property name="font"> + <font> + <weight>50</weight> + <bold>false</bold> + </font> + </property> + </widget> </item> </layout> </item> @@ -164,7 +263,14 @@ </widget> </item> <item> - <widget class="QComboBox" name="combo_contacts"/> + <widget class="QComboBox" name="combo_contacts"> + <property name="font"> + <font> + <weight>50</weight> + <bold>false</bold> + </font> + </property> + </widget> </item> </layout> </item> @@ -184,6 +290,12 @@ <layout class="QVBoxLayout" name="verticalLayout_8"> <item> <widget class="QLabel" name="label_total"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> <property name="text"> <string>Available money: </string> </property> @@ -193,6 +305,12 @@ <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> <widget class="QLabel" name="label_3"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> <property name="text"> <string>Amount</string> </property> @@ -248,23 +366,95 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_3"> - <property name="title"> - <string>Transaction message</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QLineEdit" name="edit_message"> - <property name="inputMask"> - <string/> - </property> - </widget> - </item> - </layout> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="title"> + <string>Message</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <widget class="QLineEdit" name="edit_message"> + <property name="font"> + <font> + <weight>50</weight> + <bold>false</bold> + </font> + </property> + <property name="inputMask"> + <string/> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="title"> + <string>Spend condition</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <widget class="QComboBox" name="combo_locks"> + <property name="font"> + <font> + <weight>50</weight> + <bold>false</bold> + </font> + </property> + <item> + <property name="text"> + <string>Receiver signature</string> + </property> + </item> + <item> + <property name="text"> + <string>Receiver signature or (sender after one week)</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + </layout> </item> <item> <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> <property name="title"> <string>Secret Key / Password</string> </property> diff --git a/src/sakia/gui/sub/transfer/view.py b/src/sakia/gui/sub/transfer/view.py index 53c10e63b41e652b6cd72bd4ca27dcf530106cdf..45e1fd31f7ef49c3036635bfbd76464813e16140 100644 --- a/src/sakia/gui/sub/transfer/view.py +++ b/src/sakia/gui/sub/transfer/view.py @@ -18,6 +18,7 @@ class TransferView(QWidget, Ui_TransferMoneyWidget): WRONG_PASSWORD = 2 NO_RECEIVER = 3 WRONG_RECIPIENT = 4 + SOURCE_LOCKED = 5 class RecipientMode(Enum): PUBKEY = 1 @@ -45,6 +46,10 @@ class TransferView(QWidget, Ui_TransferMoneyWidget): False, QT_TRANSLATE_NOOP("TransferView", "Incorrect receiver address or pubkey"), ), + ButtonBoxState.SOURCE_LOCKED: ( + False, + QT_TRANSLATE_NOOP("TransferView", "Source locked"), + ), } def __init__( @@ -60,7 +65,9 @@ class TransferView(QWidget, Ui_TransferMoneyWidget): super().__init__(parent) self.setupUi(self) - regexp = QRegExp("^([ a-zA-Z0-9-_:/;*?\[\]\(\)\\\?!^+=@&~#{}|<>%.]{0,255})$") + regexp = QRegExp( + "^([ a-zA-Z0-9-_:/;*?\\[\\]\\(\\)\\\\?!^+=@&~#{}|<>%.]{0,255})$" + ) validator = QRegExpValidator(regexp) self.edit_message.setValidator(validator) diff --git a/src/sakia/gui/widgets/context_menu.py b/src/sakia/gui/widgets/context_menu.py index 6f01348d7092c9e11c4a9f639288ec46bf644b6d..084ac3c263243b44e70c33de59074572007962ec 100644 --- a/src/sakia/gui/widgets/context_menu.py +++ b/src/sakia/gui/widgets/context_menu.py @@ -5,7 +5,9 @@ from PyQt5.QtWidgets import QMenu, QAction, QApplication, QMessageBox from duniterpy.constants import PUBKEY_REGEX from duniterpy.documents.crc_pubkey import CRCPubkey -from sakia.data.entities import Identity, Transaction, Dividend + +from sakia.app import Application +from sakia.data.entities import Identity, Transaction, Dividend, Connection, Source from sakia.data.processors import BlockchainProcessor, TransactionsProcessor from sakia.decorators import asyncify from sakia.gui.sub.certification.controller import CertificationController @@ -17,7 +19,7 @@ class ContextMenu(QObject): view_identity_in_wot = pyqtSignal(object) identity_information_loaded = pyqtSignal(Identity) - def __init__(self, qmenu, app, connection): + def __init__(self, qmenu: QMenu, app: Application, connection: Connection): """ :param PyQt5.QtWidgets.QMenu: the qmenu widget :param sakia.app.Application app: Application instance @@ -25,7 +27,7 @@ class ContextMenu(QObject): """ super().__init__() self.qmenu = qmenu - self._app = app + self._app = app # type: Application self._connection = connection @staticmethod @@ -104,7 +106,7 @@ class ContextMenu(QObject): menu.qmenu.addAction(copy_selfcert) @staticmethod - def _add_transfers_actions(menu, transfer): + def _add_transfer_actions(menu, transfer): """ :param ContextMenu menu: the qmenu to add actions to :param Transfer transfer: the transfer @@ -130,6 +132,26 @@ class ContextMenu(QObject): ) menu.qmenu.addAction(cancel) + # if special lock condition on transaction or + # all transaction received in expert mode... + if transfer.conditions is not None or ( + transfer.pubkey in transfer.receivers and menu._app.parameters.expert_mode + ): + # get source from conditions and transaction hash + source = menu._app.sources_service.get_one( + identifier=transfer.sha_hash, conditions=transfer.conditions + ) + if source: + # add send as source menu + send_as_source = QAction( + QCoreApplication.translate("ContextMenu", "Send as source"), + menu.qmenu.parent(), + ) + send_as_source.triggered.connect( + lambda checked, src=source: menu.send_as_source(src) + ) + menu.qmenu.addAction(send_as_source) + if menu._app.parameters.expert_mode: copy_doc = QAction( QCoreApplication.translate( @@ -156,6 +178,32 @@ class ContextMenu(QObject): ) menu.qmenu.addAction(copy_doc) + @staticmethod + def _add_dividend_actions(menu, dividend): + """ + :param ContextMenu menu: the qmenu to add actions to + :param Transfer transfer: the transfer + """ + menu.qmenu.addSeparator().setText( + QCoreApplication.translate("ContextMenu", "Dividend") + ) + + if menu._app.parameters.expert_mode: + # get dividend source from block number and type + source = menu._app.sources_service.get_one( + noffset=dividend.block_number, type=Source.TYPE_DIVIDEND + ) + if source: + # add send as source menu + send_as_source = QAction( + QCoreApplication.translate("ContextMenu", "Send as source"), + menu.qmenu.parent(), + ) + send_as_source.triggered.connect( + lambda checked, src=source: menu.send_dividend_as_source(src) + ) + menu.qmenu.addAction(send_as_source) + @staticmethod def _add_string_actions(menu, str_value): if re.match(PUBKEY_REGEX, str_value): @@ -207,8 +255,8 @@ class ContextMenu(QObject): menu = cls(QMenu(parent), app, connection) build_actions = { Identity: ContextMenu._add_identity_actions, - Transaction: ContextMenu._add_transfers_actions, - Dividend: lambda m, d: None, + Transaction: ContextMenu._add_transfer_actions, + Dividend: ContextMenu._add_dividend_actions, str: ContextMenu._add_string_actions, dict: lambda m, d: None, type(None): lambda m, d: None, @@ -250,7 +298,7 @@ class ContextMenu(QObject): ) else: TransferController.send_money_to_pubkey( - None, self._app, self._connection, identity_or_pubkey + None, self._app, self._connection, identity_or_pubkey, None ) def view_wot(self, identity): @@ -286,6 +334,28 @@ This money transfer will be removed and not sent.""", self._app.db.commit() self._app.transaction_state_changed.emit(transfer) + def send_as_source(self, source: Source): + """ + Open transfer dialog with a transaction as source + + :param Source source: Source entity to send + :return: + """ + TransferController.send_money_to_pubkey( + None, self._app, self._connection, None, source + ) + + def send_dividend_as_source(self, source: Source): + """ + Open transfer dialog with a dividend as source + + :param Source source: Source entity to send + :return: + """ + TransferController.send_money_to_pubkey( + None, self._app, self._connection, None, source + ) + def copy_transaction_to_clipboard(self, tx): clipboard = QApplication.clipboard() clipboard.setText(tx.raw) diff --git a/src/sakia/services/documents.py b/src/sakia/services/documents.py index 35726cb09e14631936eb8877b0221678c7f287b8..16182f8a5b582547b499f00b88c70bc493c1d3d0 100644 --- a/src/sakia/services/documents.py +++ b/src/sakia/services/documents.py @@ -2,6 +2,7 @@ import jsonschema import attr import logging +import pypeg2 from duniterpy.key import SigningKey from duniterpy.documents import ( Certification, @@ -12,12 +13,10 @@ from duniterpy.documents import ( SIGParameter, Unlock, block_uid, - BlockUID, ) -from duniterpy.documents import Identity as IdentityDoc from duniterpy.documents import Transaction as TransactionDoc from duniterpy.documents.transaction import reduce_base -from duniterpy.grammars import output +from duniterpy.grammars.output import Condition, Operator, SIG, CSV from duniterpy.api import bma from sakia.data.entities import Identity, Transaction, Source from sakia.data.processors import ( @@ -27,9 +26,11 @@ from sakia.data.processors import ( TransactionsProcessor, SourcesProcessor, CertificationsProcessor, + ConnectionsProcessor, ) from sakia.data.connectors import BmaConnector, parse_bma_responses from sakia.errors import NotEnoughChangeError +from sakia.services.sources import SourcesServices @attr.s() @@ -51,6 +52,7 @@ class DocumentsService: _certifications_processor = attr.ib() _transactions_processor = attr.ib() _sources_processor = attr.ib() + _sources_services = attr.ib() # type: SourcesServices _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger("sakia"))) @classmethod @@ -66,6 +68,14 @@ class DocumentsService: CertificationsProcessor.instanciate(app), TransactionsProcessor.instanciate(app), SourcesProcessor.instanciate(app), + SourcesServices( + app.currency, + SourcesProcessor.instanciate(app), + ConnectionsProcessor.instanciate(app), + TransactionsProcessor.instanciate(app), + BlockchainProcessor.instanciate(app), + BmaConnector(NodesProcessor(app.db.nodes_repo), app.parameters), + ), ) def generate_identity(self, connection): @@ -286,13 +296,13 @@ class DocumentsService: return document.signed_raw(), identity - def tx_sources(self, amount, amount_base, currency, pubkey): + def tx_sources(self, amount, amount_base, currency, key: SigningKey): """ Get inputs to generate a transaction with a given amount of money :param int amount: The amount target value :param int amount_base: The amount base target value :param str currency: The community target of the transaction - :param str pubkey: The pubkey owning the sources + :param SigningKey key: The key owning the sources :return: The list of inputs to use in the transaction document """ @@ -309,7 +319,7 @@ class DocumentsService: return i amount, amount_base = reduce_base(amount, amount_base) - available_sources = self._sources_processor.available(currency, pubkey) + available_sources = self._sources_processor.available(currency, key.pubkey) if available_sources: current_base = max([src.base for src in available_sources]) value = 0 @@ -319,6 +329,13 @@ class DocumentsService: buf_sources = list(available_sources) while current_base >= 0: for s in [src for src in available_sources if src.base == current_base]: + condition = pypeg2.parse(s.conditions, Condition) + # evaluate the condition + result, _ = self._sources_services.evaluate_condition( + currency, condition, [key.pubkey], [], s.identifier + ) + if not result: + continue test_sources = sources + [s] val = current_value(test_sources, overheads) # if we have to compute an overhead @@ -354,7 +371,10 @@ class DocumentsService: current_base -= 1 raise NotEnoughChangeError( - value, currency, len(sources), amount * pow(10, amount_base) + current_value(available_sources, []), + currency, + len(available_sources), + amount * pow(10, amount_base), ) def tx_inputs(self, sources): @@ -381,9 +401,10 @@ class DocumentsService: unlocks.append(Unlock(i, [SIGParameter(0)])) return unlocks - def tx_outputs(self, issuer, receiver, outputs, overheads): + def tx_outputs(self, issuer, receiver, outputs, overheads, lock_mode): """ Get outputs to generate a transaction with a given amount of money + :param int lock_mode: Index of the selected spend condition :param str issuer: The issuer of the transaction :param str receiver: The target of the transaction :param list outputs: The amount to send @@ -391,6 +412,22 @@ class DocumentsService: :param list overheads: The overheads used to send the given amount of money :return: The list of outputs to use in the transaction document """ + lock_modes = { + # Receiver + 0: pypeg2.compose(Condition.token(SIG.token(receiver)), Condition), + # Receiver or (issuer and delay of one week) + 1: pypeg2.compose( + Condition.token( + SIG.token(receiver), + Operator.token("||"), + Condition.token( + SIG.token(issuer), Operator.token("&&"), CSV.token(604800), + ), + ), + Condition, + ), + } + total = [] outputs_bases = set(o[1] for o in outputs) for base in outputs_bases: @@ -400,15 +437,7 @@ class DocumentsService: output_sum += o[0] # fixme: OutputSource condition argument should be an instance of Condition, not a string # it is not to the user to construct the condition script, but to the dedicated classes - total.append( - OutputSource( - output_sum, - base, - output.Condition.token(output.SIG.token(receiver)).compose( - output.Condition() - ), - ) - ) + total.append(OutputSource(output_sum, base, lock_modes[lock_mode])) overheads_bases = set(o[1] for o in overheads) for base in overheads_bases: @@ -422,9 +451,7 @@ class DocumentsService: OutputSource( overheads_sum, base, - output.Condition.token(output.SIG.token(issuer)).compose( - output.Condition() - ), + pypeg2.compose(Condition.token(SIG.token(issuer)), Condition), ) ) @@ -439,20 +466,32 @@ class DocumentsService: :return: """ for offset, output in enumerate(txdoc.outputs): - if output.condition.left.pubkey == pubkey: + if self._sources_services.find_signature_in_condition( + output.condition, pubkey + ): source = Source( currency=currency, pubkey=pubkey, identifier=txdoc.sha_hash, - type="T", + type=Source.TYPE_TRANSACTION, noffset=offset, amount=output.amount, base=output.base, + conditions=pypeg2.compose(output.condition, Condition), ) self._sources_processor.insert(source) def prepare_tx( - self, key, receiver, blockstamp, amount, amount_base, message, currency + self, + key, + receiver, + blockstamp, + amount, + amount_base, + message, + currency, + lock_mode=0, + source=None, ): """ Prepare a simple Transaction document @@ -463,38 +502,48 @@ class DocumentsService: :param int amount_base: the amount base of the currency :param str message: the comment of the tx :param str currency: the target community + :param int lock_mode: Lock condition mode selected in combo box + :param Source source: Source instance or None :return: the transaction document :rtype: List[sakia.data.entities.Transaction] """ forged_tx = [] - sources = [None] * 41 - while len(sources) > 40: - result = self.tx_sources(int(amount), amount_base, currency, key.pubkey) - sources = result[0] - computed_outputs = result[1] - overheads = result[2] - # Fix issue #594 - if len(sources) > 40: - sources_value = 0 - for s in sources[:39]: - sources_value += s.amount * (10 ** s.base) - sources_value, sources_base = reduce_base(sources_value, 0) - chained_tx = self.prepare_tx( - key, - key.pubkey, - blockstamp, - sources_value, - sources_base, - "[CHAINED]", - currency, - ) - forged_tx += chained_tx - self._sources_processor.consume(sources) + if source is None: + # automatic selection of sources + sources = [None] * 41 + while len(sources) > 40: + result = self.tx_sources(int(amount), amount_base, currency, key) + sources = result[0] + computed_outputs = result[1] + overheads = result[2] + # Fix issue #594 + if len(sources) > 40: + sources_value = 0 + for s in sources[:39]: + sources_value += s.amount * (10 ** s.base) + sources_value, sources_base = reduce_base(sources_value, 0) + chained_tx = self.prepare_tx( + key, + key.pubkey, + blockstamp, + sources_value, + sources_base, + "[CHAINED]", + currency, + ) + forged_tx += chained_tx + else: + sources = [source] + computed_outputs = [(source.amount, source.base)] + overheads = [] + logging.debug("Inputs: {0}".format(sources)) inputs = self.tx_inputs(sources) unlocks = self.tx_unlocks(sources) - outputs = self.tx_outputs(key.pubkey, receiver, computed_outputs, overheads) + outputs = self.tx_outputs( + key.pubkey, receiver, computed_outputs, overheads, lock_mode + ) logging.debug("Outputs: {0}".format(outputs)) txdoc = TransactionDoc( 10, @@ -530,13 +579,26 @@ class DocumentsService: raw=txdoc.signed_raw(), ) forged_tx.append(tx) + + self._sources_processor.consume(sources, tx.sha_hash) + return forged_tx async def send_money( - self, connection, secret_key, password, recipient, amount, amount_base, message + self, + connection, + secret_key, + password, + recipient, + amount, + amount_base, + message, + lock_mode, + source, ): """ Send money to a given recipient in a specified community + :param int lock_mode: Index in the combo_locks combobox :param sakia.data.entities.Connection connection: The account salt :param str secret_key: The account secret_key :param str password: The account password @@ -544,6 +606,7 @@ class DocumentsService: :param int amount: The amount of money to transfer :param int amount_base: The amount base of the transfer :param str message: The message to send with the transfer + :param Source source: Source instance or None """ blockstamp = self._blockchain_processor.current_buid(connection.currency) key = SigningKey.from_credentials( @@ -561,6 +624,8 @@ class DocumentsService: amount_base, message, connection.currency, + lock_mode, + source, ) for i, tx in enumerate(tx_entities): diff --git a/src/sakia/services/sources.py b/src/sakia/services/sources.py index 9c6b47ae6d8354168c2823404bbd1dbeca643d63..d1c5984e1b44ee10620b03f98943c5de74d536ee 100644 --- a/src/sakia/services/sources.py +++ b/src/sakia/services/sources.py @@ -1,13 +1,24 @@ -from PyQt5.QtCore import QObject +from PyQt5.QtCore import QObject, QT_TRANSLATE_NOOP from duniterpy.api import bma, errors from duniterpy.documents import Transaction as TransactionDoc -from duniterpy.grammars.output import Condition +from duniterpy.grammars.output import Condition, SIG, CSV, CLTV, XHX from duniterpy.documents import BlockUID import logging import pypeg2 -from sakia.data.entities import Source, Transaction, Dividend +from sakia.data.entities import Source, Transaction import hashlib +EVALUATE_CONDITION_ERROR_SIG = QT_TRANSLATE_NOOP( + "SourcesServices", "missing secret key for public key" +) +EVALUATE_CONDITION_ERROR_XHX = QT_TRANSLATE_NOOP( + "SourcesServices", "missing password for hash" +) +EVALUATE_CONDITION_ERROR_CSV = QT_TRANSLATE_NOOP( + "SourcesServices", "locked by a delay until" +) +EVALUATE_CONDITION_ERROR_CLTV = QT_TRANSLATE_NOOP("SourcesServices", "locked until") + class SourcesServices(QObject): """ @@ -43,31 +54,39 @@ class SourcesServices(QObject): self.currency = currency self._logger = logging.getLogger("sakia") + def get_one(self, **search): + return self._sources_processor.get_one(**search) + def amount(self, pubkey): return self._sources_processor.amount(self.currency, pubkey) def parse_transaction_outputs(self, pubkey, transaction): """ - Parse a transaction + Parse a transaction to extract sources + + :param str pubkey: Receiver pubkey :param sakia.data.entities.Transaction transaction: """ txdoc = TransactionDoc.from_signed_raw(transaction.raw) for offset, output in enumerate(txdoc.outputs): - if output.condition.left.pubkey == pubkey: + if self.find_signature_in_condition(output.condition, pubkey): source = Source( currency=self.currency, pubkey=pubkey, identifier=txdoc.sha_hash, - type="T", + type=Source.TYPE_TRANSACTION, noffset=offset, amount=output.amount, base=output.base, + conditions=pypeg2.compose(output.condition, Condition), ) self._sources_processor.insert(source) - def parse_transaction_inputs(self, pubkey, transaction): + def consume_sources_from_transaction_inputs(self, pubkey, transaction): """ - Parse a transaction + Parse a transaction to drop sources used in inputs + + :param str pubkey: Receiver pubkey :param sakia.data.entities.Transaction transaction: """ txdoc = TransactionDoc.from_signed_raw(transaction.raw) @@ -80,24 +99,29 @@ class SourcesServices(QObject): noffset=input.index, amount=input.amount, base=input.base, + conditions="", + used_by=None, ) if source.pubkey == pubkey: - self._sources_processor.drop(source) + self._sources_processor.consume((source,), transaction.sha_hash) def _parse_ud(self, pubkey, dividend): """ - :param str pubkey: - :param sakia.data.entities.Dividend dividend: + Add source in db from UD + + :param str pubkey: Pubkey of UD issuer + :param sakia.data.entities.Dividend dividend: Dividend instance :return: """ source = Source( currency=self.currency, pubkey=pubkey, identifier=pubkey, - type="D", + type=Source.TYPE_DIVIDEND, noffset=dividend.block_number, amount=dividend.amount, base=dividend.base, + conditions=pypeg2.compose(Condition.token(SIG.token(pubkey)), Condition), ) self._sources_processor.insert(source) @@ -106,25 +130,10 @@ class SourcesServices(QObject): self.currency, bma.tx.sources, req_args={"pubkey": pubkey} ) nb_sources = len(sources_data["sources"]) - for i, s in enumerate(sources_data["sources"]): - log_stream("Parsing source ud/tx {:}/{:}".format(i, nb_sources)) + for index, source in enumerate(sources_data["sources"]): + log_stream("Parsing source ud/tx {:}/{:}".format(index, nb_sources)) progress(1 / nb_sources) - conditions = pypeg2.parse(s["conditions"], Condition) - if conditions.left.pubkey == pubkey: - try: - if conditions.left.pubkey == pubkey: - source = Source( - currency=self.currency, - pubkey=pubkey, - identifier=s["identifier"], - type=s["type"], - noffset=s["noffset"], - amount=s["amount"], - base=s["base"], - ) - self._sources_processor.insert(source) - except AttributeError as e: - self._logger.error(str(e)) + self.add_source(pubkey, source) async def check_destruction(self, pubkey, block_number, unit_base): amount = self._sources_processor.amount(self.currency, pubkey) @@ -177,23 +186,8 @@ class SourcesServices(QObject): self.currency, bma.tx.sources, req_args={"pubkey": pubkey} ) self._sources_processor.drop_all_of(self.currency, pubkey) - for i, s in enumerate(sources_data["sources"]): - conditions = pypeg2.parse(s["conditions"], Condition) - if conditions.left.pubkey == pubkey: - try: - if conditions.left.pubkey == pubkey: - source = Source( - currency=self.currency, - pubkey=pubkey, - identifier=s["identifier"], - type=s["type"], - noffset=s["noffset"], - amount=s["amount"], - base=s["base"], - ) - self._sources_processor.insert(source) - except AttributeError as e: - self._logger.error(str(e)) + for source in sources_data["sources"]: + self.add_source(pubkey, source) async def refresh_sources(self, connections): """ @@ -213,31 +207,271 @@ class SourcesServices(QObject): def restore_sources(self, pubkey, tx): """ - Restore the sources of a cancelled tx - :param sakia.entities.Transaction tx: + Restore consumed sources and drop created sources of a cancelled tx + + :param str pubkey: Pubkey + :param Transaction tx: Instance of tx entity """ txdoc = TransactionDoc.from_signed_raw(tx.raw) for offset, output in enumerate(txdoc.outputs): - if output.condition.left.pubkey == pubkey: - source = Source( - currency=self.currency, - pubkey=pubkey, - identifier=txdoc.sha_hash, - type="T", - noffset=offset, - amount=output.amount, - base=output.base, - ) - self._sources_processor.drop(source) - for index, input in enumerate(txdoc.inputs): source = Source( currency=self.currency, - pubkey=txdoc.issuers[0], - identifier=input.origin_id, - type=input.source, - noffset=input.index, - amount=input.amount, - base=input.base, + pubkey=pubkey, + identifier=txdoc.sha_hash, + type=Source.TYPE_TRANSACTION, + noffset=offset, + amount=output.amount, + base=output.base, + conditions=pypeg2.compose(output.condition, Condition), ) - if source.pubkey == pubkey: - self._sources_processor.insert(source) + # drop sources created by the canceled tx + self._sources_processor.drop(source) + # restore consumed sources + self._sources_processor.restore_all(tx.sha_hash) + + def find_signature_in_condition(self, _condition, pubkey, result=False): + """ + Recursive function to find a SIG(pubkey) in a Condition object + + :param Condition _condition: Condition instance + :param str pubkey: Pubkey to find + :param bool result: True if found + :return: + """ + if isinstance(_condition.left, Condition): + result |= self.find_signature_in_condition(_condition.left, pubkey, result) + if isinstance(_condition.right, Condition): + result |= self.find_signature_in_condition(_condition.right, pubkey, result) + if ( + isinstance(_condition.left, SIG) + and _condition.left.pubkey == pubkey + or isinstance(_condition.right, SIG) + and _condition.right.pubkey == pubkey + ): + result |= True + return result + + def add_source(self, pubkey, source): + """ + Add a new source for the pubkey + + :param str pubkey: Pubkey concerned + :param dict source: Source dict from api + :return: + """ + try: + entity = Source( + currency=self.currency, + pubkey=pubkey, + identifier=source["identifier"], + type=source["type"], + noffset=source["noffset"], + amount=source["amount"], + base=source["base"], + conditions=source["conditions"], + used_by=None, + ) + self._sources_processor.insert(entity) + except AttributeError as e: + self._logger.error(str(e)) + + def evaluate_condition( + self, + currency: str, + condition: Condition, + pubkeys: list, + passwords: list, + identifier: str, + result: bool = False, + _errors: list = None, + ) -> tuple: + """ + Evaluate a source lock condition + Support multiple signatures and passwords + + :param str currency: Name of currency + :param Condition condition: Condition instance + :param [str] pubkeys: Keys to unlock condition + :param [str] passwords: List of passwords + :param str identifier: Source transaction identifier + :param bool result: Evaluation result accumulator + :param [tuple] _errors: List of tuple with infos on condition returning false (condition: str, message: str, + param: str|int) + :return: + """ + left = False + right = False + # if left param is a condition... + if isinstance(condition.left, Condition): + # evaluate condition + left, _errors = self.evaluate_condition( + currency, + condition.left, + pubkeys, + passwords, + identifier, + result, + _errors, + ) + # if right param is a condition... + if isinstance(condition.right, Condition): + # evaluate condition + right, _errors = self.evaluate_condition( + currency, + condition.right, + pubkeys, + passwords, + identifier, + result, + _errors, + ) + # if left param is a SIG... + if isinstance(condition.left, SIG): + if condition.left.pubkey in pubkeys: + left = True + else: + if _errors is None: + _errors = [] + _errors.append( + ( + pypeg2.compose(condition.left), + EVALUATE_CONDITION_ERROR_SIG, + condition.left.pubkey, + ) + ) + + # if left param is a CSV value... + if isinstance(condition.left, CSV): + # capture transaction of the source + tx = self._transactions_processor.find_one_by_hash(identifier) + if tx: + # capture current blockchain time + median_time = self._blockchain_processor.time(currency) + locked_until = tx.timestamp + int(condition.left.time) + # param is true if tx time + CSV delay <= blockchain time + left = locked_until <= median_time + if left is False: + if _errors is None: + _errors = [] + _errors.append( + ( + pypeg2.compose(condition.left), + EVALUATE_CONDITION_ERROR_CSV, + locked_until, + ) + ) + + # if left param is a CLTV value... + if isinstance(condition.left, CLTV): + # capture current blockchain time + median_time = self._blockchain_processor.time(currency) + locked_until = int(condition.left.timestamp) + # param is true if CL:TV value <= blockchain time + left = locked_until <= median_time + if left is False: + if _errors is None: + _errors = [] + _errors.append( + ( + pypeg2.compose(condition.left), + EVALUATE_CONDITION_ERROR_CLTV, + locked_until, + ) + ) + + # if left param is a XHX value... + if isinstance(condition.left, XHX): + left = condition.left.sha_hash in [ + hashlib.sha256(password).hexdigest().upper() for password in passwords + ] + if left is False: + if _errors is None: + _errors = [] + _errors.append( + ( + pypeg2.compose(condition.left), + EVALUATE_CONDITION_ERROR_XHX, + condition.left.sha_hash, + ) + ) + + # if no op then stop evaluation... + if not condition.op: + return left, _errors + + # if right param is a SIG... + if isinstance(condition.right, SIG): + if condition.right.pubkey in pubkeys: + right = True + else: + if _errors is None: + _errors = [] + _errors.append( + ( + pypeg2.compose(condition.right), + EVALUATE_CONDITION_ERROR_SIG, + condition.right.pubkey, + ) + ) + + # if right param is a CSV value... + if isinstance(condition.right, CSV): + # capture transaction of the source + tx = self._transactions_processor.find_one_by_hash(identifier) + if tx: + # capture current blockchain time + median_time = self._blockchain_processor.time(currency) + locked_until = tx.timestamp + int(condition.right.time) + # param is true if tx time + CSV delay <= blockchain time + right = locked_until <= median_time + if right is False: + if _errors is None: + _errors = [] + _errors.append( + ( + pypeg2.compose(condition.right), + EVALUATE_CONDITION_ERROR_CSV, + locked_until, + ) + ) + + # if right param is a CLTV value... + if isinstance(condition.right, CLTV): + # capture current blockchain time + median_time = self._blockchain_processor.time(currency) + locked_until = int(condition.right.timestamp) + # param is true if CL:TV value <= blockchain time + right = locked_until <= median_time + if right is False: + if _errors is None: + _errors = [] + _errors.append( + ( + pypeg2.compose(condition.right), + EVALUATE_CONDITION_ERROR_CLTV, + locked_until, + ) + ) + + # if right param is a XHX value... + if isinstance(condition.right, XHX): + right = condition.right.sha_hash in [ + hashlib.sha256(password).hexdigest().upper() for password in passwords + ] + if right is False: + if _errors is None: + _errors = [] + _errors.append( + ( + pypeg2.compose(condition.right), + EVALUATE_CONDITION_ERROR_XHX, + condition.right.sha_hash, + ) + ) + + # if operator AND... + if condition.op == "&&": + return left & right, _errors + + # operator OR + return left | right, _errors diff --git a/src/sakia/services/transactions.py b/src/sakia/services/transactions.py index 637bff4cce5fc82945366529a569b0476247b9d6..b525a97e5bb71ce05421127fb86d568464c75373 100644 --- a/src/sakia/services/transactions.py +++ b/src/sakia/services/transactions.py @@ -8,7 +8,7 @@ from sakia.data.entities.transaction import ( ) from duniterpy.documents import Transaction as TransactionDoc from duniterpy.documents import SimpleTransaction, Block -from sakia.data.entities import Dividend +from sakia.data.entities import Dividend, Source from duniterpy.api import bma import logging @@ -53,7 +53,9 @@ class TransactionsService(QObject): :param sakia.data.entities.Transaction transaction: The transaction to parse :return: The list of transfers sent """ - if not self._transactions_processor.find_by_hash(pubkey, transaction.sha_hash): + if not self._transactions_processor.find_by_pubkey_and_hash( + pubkey, transaction.sha_hash + ): txid = self._transactions_processor.next_txid(transaction.currency, -1) tx = parse_transaction_doc( transaction.txdoc(), @@ -123,7 +125,7 @@ class TransactionsService(QObject): tx_doc = TransactionDoc.from_bma_history( history_data["currency"], tx_data ) - if not self._transactions_processor.find_by_hash( + if not self._transactions_processor.find_by_pubkey_and_hash( connection.pubkey, tx_doc.sha_hash ) and SimpleTransaction.is_simple(tx_doc): tx = parse_transaction_doc( @@ -177,7 +179,7 @@ class TransactionsService(QObject): for input in txdoc.inputs: # For each dividends inputs, if it is consumed (not present in ud history) if ( - input.source == "D" + input.source == Source.TYPE_DIVIDEND and input.origin_id == connection.pubkey and input.index not in block_numbers ): @@ -233,7 +235,7 @@ class TransactionsService(QObject): ) for tx_data in history_data["history"]["received"]: tx_doc = TransactionDoc.from_bma_history(history_data["currency"], tx_data) - if not self._transactions_processor.find_by_hash( + if not self._transactions_processor.find_by_pubkey_and_hash( pubkey, tx_doc.sha_hash ) and SimpleTransaction.is_simple(tx_doc): tx = parse_transaction_doc( @@ -290,7 +292,7 @@ class TransactionsService(QObject): for input in txdoc.inputs: # For each dividends inputs, if it is consumed (not present in ud history) if ( - input.source == "D" + input.source == Source.TYPE_DIVIDEND and input.origin_id == pubkey and input.index not in block_numbers ): diff --git a/tests/conftest.py b/tests/conftest.py index 7ac7557c6475f822e4868a58b16bc314b54fd5f7..1e69c829baefbf74d5f750a4aff0d0f0bdbab259 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import pypeg2 import pytest import asyncio import quamash @@ -6,6 +7,8 @@ import mirage import sys import os +from duniterpy.grammars import output + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) from sakia.constants import ROOT_SERVERS @@ -43,7 +46,13 @@ def meta_repo(version=0): sqlite3.register_adapter(BlockUID, str) sqlite3.register_adapter(bool, int) sqlite3.register_converter("BOOLEAN", lambda v: bool(int(v))) + + def total_amount(amount, amount_base): + return amount * 10 ** amount_base + con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES) + con.create_function("total_amount", 2, total_amount) + meta_repo = SakiaDatabase( con, ConnectionsRepo(con), @@ -260,6 +269,10 @@ def application_with_one_connection(application, simple_blockchain_forge, bob): type=s.source, amount=s.amount, base=s.base, + conditions=pypeg2.compose( + output.Condition.token(output.SIG.token(bob.key.pubkey)), + output.Condition, + ), ) ) bob_blockstamp = simple_blockchain_forge.user_identities[bob.key.pubkey].blockstamp @@ -314,6 +327,10 @@ def application_with_two_connections( type=s.source, amount=s.amount, base=s.base, + conditions=pypeg2.compose( + output.Condition.token(output.SIG.token(bob.key.pubkey)), + output.Condition, + ), ) ) except sqlite3.IntegrityError: diff --git a/tests/functional/test_transfer_dialog.py b/tests/functional/test_transfer_dialog.py index 52a313154991e5853fb6045b249b6fa3bb72470a..b156c89964b555755a741cdd68962c25099b212c 100644 --- a/tests/functional/test_transfer_dialog.py +++ b/tests/functional/test_transfer_dialog.py @@ -74,3 +74,43 @@ async def test_transfer_chained_tx( transfer_dialog.view.show() await exec_test() + + +@pytest.mark.asyncio +async def test_transfer_from_source( + application_with_one_connection, fake_server_with_blockchain, bob, alice +): + def close_dialog(): + if transfer_dialog.view.isVisible(): + transfer_dialog.view.hide() + + async def exec_test(): + await asyncio.sleep(0.1) + assert not transfer_dialog.view.button_box.button( + QDialogButtonBox.Ok + ).isEnabled() + assert transfer_dialog.view.spinbox_amount.value() == 1 + await asyncio.sleep(0.1) + # simulate checked source condition + transfer_dialog.view.button_source_check.setDisabled(True) + QTest.keyClicks(transfer_dialog.view.password_input.edit_secret_key, bob.salt) + QTest.keyClicks(transfer_dialog.view.password_input.edit_password, bob.password) + assert transfer_dialog.view.button_box.button(QDialogButtonBox.Ok).isEnabled() + QTest.mouseClick( + transfer_dialog.view.button_box.button(QDialogButtonBox.Ok), Qt.LeftButton + ) + await asyncio.sleep(0.2) + assert isinstance(fake_server_with_blockchain.forge.pool[0], Transaction) + + application_with_one_connection.loop.call_later(10, close_dialog) + + bob_connection = application_with_one_connection.db.connections_repo.get_one( + pubkey=bob.key.pubkey + ) + source = application_with_one_connection.sources_service.get_one( + pubkey=bob.key.pubkey + ) + transfer_dialog = TransferController.open_transfer_with_pubkey( + None, application_with_one_connection, bob_connection, alice.key.pubkey, source + ) + await exec_test() diff --git a/tests/functional/test_txhistory_controller.py b/tests/functional/test_txhistory_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..2cefdf17386ff2ae2fdbab61e7a67c989683e77a --- /dev/null +++ b/tests/functional/test_txhistory_controller.py @@ -0,0 +1,74 @@ +import time +import pytest +from sakia.gui.navigation.txhistory.table_model import HistoryTableModel + + +@pytest.mark.asyncio +async def test_tx_history_table_model( + application_with_one_connection, fake_server_with_blockchain, bob, alice +): + application_with_one_connection.instanciate_services() + + bob_connection = application_with_one_connection.db.connections_repo.get_one( + pubkey=bob.key.pubkey + ) + + date_start = time.time() - 86400 + date_end = time.time() + + history_table_model = HistoryTableModel( + application_with_one_connection, + application_with_one_connection, + bob_connection, + date_start, + date_end, + application_with_one_connection.identities_service, + application_with_one_connection.transactions_service, + ) + + # send transaction with lock_mode 0 + ( + _, + sakia_tx_list, + ) = await application_with_one_connection.documents_service.send_money( + bob_connection, bob.salt, bob.password, alice.key.pubkey, 100, 0, None, 0, None + ) + history_table_model.init_transfers() + + transfers = application_with_one_connection.transactions_service.transfers( + bob.key.pubkey + ) + + # test transfer change + history_table_model.change_transfer(transfers[0]) + + assert len(history_table_model.transfers_data) == 1 + conditions_data = history_table_model.transfers_data[0][ + HistoryTableModel.columns_types.index("conditions") + ] + assert conditions_data is None + + # send transaction with lock_mode_1 + ( + _, + sakia_tx_list, + ) = await application_with_one_connection.documents_service.send_money( + bob_connection, bob.salt, bob.password, alice.key.pubkey, 100, 0, None, 1, None + ) + history_table_model.init_transfers() + + transfers = application_with_one_connection.transactions_service.transfers( + bob.key.pubkey + ) + + # test transfer change + history_table_model.change_transfer(transfers[0]) + + assert len(history_table_model.transfers_data) == 2 + conditions_data = history_table_model.transfers_data[1][ + HistoryTableModel.columns_types.index("conditions") + ] + assert ( + conditions_data + == "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo) || (SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7) && CSV(604800))" + ) diff --git a/tests/technical/test_documents_service.py b/tests/technical/test_documents_service.py index 4ecdda858d9fc33a48adfc27715d88c72b3ff333..99e94e3e5f47f34cffedb1306e9986df09feb2aa 100644 --- a/tests/technical/test_documents_service.py +++ b/tests/technical/test_documents_service.py @@ -43,6 +43,8 @@ async def test_send_more_than_40_sources( amount_before_send, 0, "Test comment", + 0, + None, ) assert transactions[0].comment == "[CHAINED]" assert transactions[1].comment == "Test comment" diff --git a/tests/technical/test_sources_service.py b/tests/technical/test_sources_service.py index 2cc8c698fc218beb25d6a17eb98738a252b7f425..553741bf3b74d9416fd40f12037db025a6ebff3a 100644 --- a/tests/technical/test_sources_service.py +++ b/tests/technical/test_sources_service.py @@ -83,7 +83,7 @@ async def test_send_tx_then_cancel( ) fake_server_with_blockchain.reject_next_post = True await application_with_one_connection.documents_service.send_money( - bob_connection, bob.salt, bob.password, alice.key.pubkey, 10, 0, "Test comment" + bob_connection, bob.salt, bob.password, alice.key.pubkey, 10, 0, None, 0, None ) tx_after_send = application_with_one_connection.transactions_service.transfers( bob.key.pubkey diff --git a/tests/technical/test_transactions_service.py b/tests/technical/test_transactions_service.py index b6b073dffe6d5db3a278a15f0352a0ed6acca160..944f3477e136476cd9f26bcda61ba87a42cb426e 100644 --- a/tests/technical/test_transactions_service.py +++ b/tests/technical/test_transactions_service.py @@ -18,7 +18,7 @@ async def test_send_tx_then_validate( pubkey=bob.key.pubkey ) await application_with_one_connection.documents_service.send_money( - bob_connection, bob.salt, bob.password, alice.key.pubkey, 10, 0, "Test comment" + bob_connection, bob.salt, bob.password, alice.key.pubkey, 10, 0, None, 0 ) tx_after_send = application_with_one_connection.transactions_service.transfers( bob.key.pubkey diff --git a/tests/unit/data/test_sources_repo.py b/tests/unit/data/test_sources_repo.py index 858011baf228f9939472f0f7ba2e5d8f107ad1e7..4c413dd0436cf3766cc239885c276cdf52d8c005 100644 --- a/tests/unit/data/test_sources_repo.py +++ b/tests/unit/data/test_sources_repo.py @@ -13,6 +13,8 @@ def test_add_get_drop_source(meta_repo): "T", 1565, 1, + "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)", + None, ) ) source = sources_repo.get_one( @@ -43,6 +45,8 @@ def test_add_get_multiple_source(meta_repo): "T", 1565, 1, + "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)", + None, ) ) sources_repo.insert( @@ -54,6 +58,8 @@ def test_add_get_multiple_source(meta_repo): "D", 726946, 1, + "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)", + None, ) ) sources = sources_repo.get_all( @@ -71,3 +77,41 @@ def test_add_get_multiple_source(meta_repo): assert "0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843" in [ s.identifier for s in sources ] + + +def test_consume_restore_source(meta_repo): + tx_hash = "0835CEE9B4766B3866DD942971B3EE2CF953599EB9D35BFD5F1345879498B843" + sources_repo = SourcesRepo(meta_repo.conn) + sources_repo.insert( + Source( + "testcurrency", + "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + tx_hash, + 3, + "T", + 1565, + 1, + "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)", + None, + ) + ) + source = sources_repo.get_one(identifier=tx_hash) + assert source.currency == "testcurrency" + assert source.pubkey == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" + assert source.type == "T" + assert source.amount == 1565 + assert source.base == 1 + assert source.noffset == 3 + + sources_repo.consume(source, tx_hash) + source = sources_repo.get_one(identifier=tx_hash) + assert source is None + + sources_repo.restore_all(tx_hash) + source = sources_repo.get_one(identifier=tx_hash) + assert source.currency == "testcurrency" + assert source.pubkey == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" + assert source.type == "T" + assert source.amount == 1565 + assert source.base == 1 + assert source.noffset == 3 diff --git a/tests/unit/services/test_documents.py b/tests/unit/services/test_documents.py new file mode 100644 index 0000000000000000000000000000000000000000..562543d0e0b1fb947e2d66ca54797b2d8bf2ea11 --- /dev/null +++ b/tests/unit/services/test_documents.py @@ -0,0 +1,71 @@ +import pytest + + +@pytest.mark.asyncio +async def test_lock_mode_0(application_with_one_connection, fake_server, bob, alice): + """ + Test the lock mode 0, Receiver signature + + :param application_with_one_connection: + :param fake_server: + :param bob: + :param alice: + :return: + """ + # check money amount in bob account + amount = application_with_one_connection.sources_service.amount(bob.key.pubkey) + assert amount == 1584 + + # create connection to have sources... + bob_connection = application_with_one_connection.db.connections_repo.get_one( + pubkey=bob.key.pubkey + ) + + # send transaction + ( + _, + sakia_tx_list, + ) = await application_with_one_connection.documents_service.send_money( + bob_connection, bob.salt, bob.password, alice.key.pubkey, 100, 0, None, 0, None + ) + + assert len(sakia_tx_list) == 1 + assert ( + "Outputs:\n100:0:SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)\n" + in sakia_tx_list[0].raw + ) + + +@pytest.mark.asyncio +async def test_lock_mode_1(application_with_one_connection, fake_server, bob, alice): + """ + Test the lock mode 0, Receiver signature OR (Sender signature AND CSV(one week delay)) + + :param application_with_one_connection: + :param fake_server: + :param bob: + :param alice: + :return: + """ + # check money amount in bob account + amount = application_with_one_connection.sources_service.amount(bob.key.pubkey) + assert amount == 1584 + + # create connection to have sources... + bob_connection = application_with_one_connection.db.connections_repo.get_one( + pubkey=bob.key.pubkey + ) + + # send transaction + ( + _, + sakia_tx_list, + ) = await application_with_one_connection.documents_service.send_money( + bob_connection, bob.salt, bob.password, alice.key.pubkey, 100, 0, None, 1, None + ) + + assert len(sakia_tx_list) == 1 + assert ( + "Outputs:\n100:0:SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo) || (SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7) && CSV(604800))\n" + in sakia_tx_list[0].raw + ) diff --git a/tests/unit/services/test_sources.py b/tests/unit/services/test_sources.py new file mode 100644 index 0000000000000000000000000000000000000000..2a098a5269c5617ff39ec9682ef08fb95da249a6 --- /dev/null +++ b/tests/unit/services/test_sources.py @@ -0,0 +1,883 @@ +import hashlib + +import pypeg2 +import pytest +from duniterpy.documents.transaction import output + +from sakia.data.entities import Transaction +from sakia.data.repositories import TransactionsRepo +from sakia.services import sources + + +def test_parse_source_condition(application_with_one_connection, alice, bob): + application_with_one_connection.instanciate_services() + + issuer = alice.key.pubkey + receiver = bob.key.pubkey + condition = output.Condition.token( + output.SIG.token(receiver), + output.Operator.token("||"), + output.Condition.token( + output.SIG.token(issuer), + output.Operator.token("&&"), + output.CSV.token(604800), + ), + ) + assert ( + application_with_one_connection.sources_service.find_signature_in_condition( + condition, receiver + ) + is True + ) + assert ( + application_with_one_connection.sources_service.find_signature_in_condition( + condition, issuer + ) + is True + ) + assert ( + application_with_one_connection.sources_service.find_signature_in_condition( + condition, "badpubkey" + ) + is False + ) + condition = output.Condition.token( + output.Condition.token( + output.CSV.token(604800), + output.Operator.token("&&"), + output.SIG.token(issuer), + ), + output.Operator.token("||"), + output.SIG.token(receiver), + ) + + assert ( + application_with_one_connection.sources_service.find_signature_in_condition( + condition, receiver + ) + is True + ) + assert ( + application_with_one_connection.sources_service.find_signature_in_condition( + condition, issuer + ) + is True + ) + assert ( + application_with_one_connection.sources_service.find_signature_in_condition( + condition, "badpubkey" + ) + is False + ) + + +def test_evaluate_condition_source_lock_mode_0( + application_with_one_connection, bob, alice, meta_repo +): + application_with_one_connection.instanciate_services() + + # capture blockchain median time + median_time = application_with_one_connection.blockchain_service._blockchain_processor.time( + application_with_one_connection.currency + ) + tx_hash = "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365" + + # add a transaction in bob tx history + transactions_repo = TransactionsRepo(meta_repo.conn) + transactions_repo.insert( + Transaction( + "testcurrency", + bob.key.pubkey, + tx_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, + bob.key.pubkey, + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + transactions_repo.insert( + Transaction( + "testcurrency", + alice.key.pubkey, + tx_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, + bob.key.pubkey, + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + # test simple signature condition + condition = output.Condition.token(output.SIG.token(bob.key.pubkey),) + # bob can spend this source + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + condition, + [bob.key.pubkey], + [], + tx_hash, + ) + assert result is True + assert _errors is None + + # alice can not + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + condition, + [alice.key.pubkey], + [], + tx_hash, + ) + assert result is False + assert _errors == [ + ( + pypeg2.compose(condition), + sources.EVALUATE_CONDITION_ERROR_SIG, + bob.key.pubkey, + ) + ] + + +def test_evaluate_condition_source_lock_mode_1( + application_with_one_connection, bob, alice, meta_repo +): + application_with_one_connection.instanciate_services() + + # capture blockchain median time + median_time = application_with_one_connection.blockchain_service._blockchain_processor.time( + application_with_one_connection.currency + ) + tx_hash = "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365" + + # add a transaction in bob tx history + transactions_repo = TransactionsRepo(meta_repo.conn) + transactions_repo.insert( + Transaction( + "testcurrency", + bob.key.pubkey, + tx_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, + bob.key.pubkey, + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + transactions_repo.insert( + Transaction( + "testcurrency", + alice.key.pubkey, + tx_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, + bob.key.pubkey, + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + + # test condition: receiver or (issuer and CSV(one week)) + delay = 604800 + condition = output.Condition.token( + output.SIG.token(bob.key.pubkey), + output.Operator.token("||"), + output.Condition.token( + output.SIG.token(alice.key.pubkey), + output.Operator.token("&&"), + output.CSV.token(delay), + ), + ) + # bob spend his source + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + condition, + [bob.key.pubkey], + [], + tx_hash, + ) + assert result is True + assert _errors == [ + ( + "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo", + ), + ("CSV(604800)", sources.EVALUATE_CONDITION_ERROR_CSV, median_time + delay), + ] + + # alice try to get back this source before the CSV delay + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + condition, + [alice.key.pubkey], + [], + tx_hash, + ) + assert result is False + assert _errors == [ + ("CSV(604800)", sources.EVALUATE_CONDITION_ERROR_CSV, median_time + delay), + ( + "SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7", + ), + ] + + # Transaction was made one week ago... + transactions_repo.update( + Transaction( + "testcurrency", + bob.key.pubkey, + tx_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time - delay, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, + bob.key.pubkey, + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + transactions_repo.update( + Transaction( + "testcurrency", + alice.key.pubkey, + tx_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time - delay, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, + bob.key.pubkey, + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + tx = transactions_repo.get_one(sha_hash=tx_hash) + assert tx.timestamp == median_time - delay + + # bob try to spend his source + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + condition, + [bob.key.pubkey], + [], + tx_hash, + ) + assert result is True + assert _errors == [ + ( + "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo", + ) + ] + + # alice can get back this source after the CSV delay + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + condition, + [alice.key.pubkey], + [], + tx_hash, + ) + assert result is True + assert _errors == [ + ( + "SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7", + ) + ] + + +def test_evaluate_condition_source_multisig( + application_with_one_connection, bob, alice, meta_repo +): + application_with_one_connection.instanciate_services() + + # capture blockchain median time + median_time = application_with_one_connection.blockchain_service._blockchain_processor.time( + application_with_one_connection.currency + ) + tx_hash = "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365" + + # add a transaction in bob tx history + transactions_repo = TransactionsRepo(meta_repo.conn) + transactions_repo.insert( + Transaction( + "testcurrency", + bob.key.pubkey, + tx_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, + bob.key.pubkey, + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + transactions_repo.insert( + Transaction( + "testcurrency", + alice.key.pubkey, + tx_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, + bob.key.pubkey, + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + + # test condition: multi signatures + condition = output.Condition.token( + output.SIG.token(bob.key.pubkey), + output.Operator.token("&&"), + output.SIG.token(alice.key.pubkey), + ) + # bob can not spend this source alone + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + condition, + [bob.key.pubkey], + [], + tx_hash, + ) + assert result is False + assert _errors == [ + ( + "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo", + ) + ] + + # alice can not spend this source alone + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + condition, + [alice.key.pubkey], + [], + tx_hash, + ) + assert result is False + assert _errors == [ + ( + "SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7", + ) + ] + + # alice && bob together only can spend this source + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + condition, + [alice.key.pubkey, bob.key.pubkey], + [], + tx_hash, + ) + assert result is True + assert _errors is None + + +def test_evaluate_condition_source_atomic_swap( + application_with_one_connection, bob, alice, meta_repo +): + application_with_one_connection.instanciate_services() + transactions_repo = TransactionsRepo(meta_repo.conn) + + # capture blockchain median time + median_time = application_with_one_connection.blockchain_service._blockchain_processor.time( + application_with_one_connection.currency + ) + + tx1_hash = "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365" + # add TX1 transaction in alice tx history + transactions_repo.insert( + Transaction( + "testcurrency", + alice.key.pubkey, # alice history + tx1_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, # do not care + bob.key.pubkey, # do not care + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + # add TX1 transaction in bob tx history + transactions_repo.insert( + Transaction( + "testcurrency", + bob.key.pubkey, # bob history + tx1_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, # do not care + bob.key.pubkey, # do not care + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + + # atomic swap initiator TX1 condition created by alice + # (XHX(password) && SIG(bob)) || (SIG(alice) && SIG(bob)) || (SIG(alice) && CSV(48h)) + password = "test".encode("utf8") # password must be encoded in str not unicode + hash_password = hashlib.sha256(password).hexdigest().upper() + delay_48h = 172800 + tx1_condition = output.Condition.token( + output.Condition.token( + output.Condition.token( + output.XHX.token(hash_password), + output.Operator.token("&&"), + output.SIG.token(bob.key.pubkey), + ), + output.Operator.token("||"), + output.Condition.token( + output.SIG.token(alice.key.pubkey), + output.Operator.token("&&"), + output.SIG.token(bob.key.pubkey), + ), + ), + output.Operator.token("||"), + output.Condition.token( + output.SIG.token(alice.key.pubkey), + output.Operator.token("&&"), + output.CSV.token(delay_48h), + ), + ) + + tx2_hash = "ACAB5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365" + # add TX2 transaction in alice tx history + transactions_repo.insert( + Transaction( + "testcurrency", + alice.key.pubkey, # alice history + tx2_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, # do not care + bob.key.pubkey, # do not care + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + # add TX2 transaction in bob tx history + transactions_repo.insert( + Transaction( + "testcurrency", + bob.key.pubkey, # bob history + tx2_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, # do not care + bob.key.pubkey, # do not care + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + + # test atomic swap participant TX2 condition created by bob with alice password hash + # (XHX(password) && SIG(alice)) || (SIG(alice) && SIG(bob)) || (SIG(bob) && CSV(24h)) + delay_24h = 86400 + tx2_condition = output.Condition.token( + output.Condition.token( + output.Condition.token( + output.XHX.token(hash_password), + output.Operator.token("&&"), + output.SIG.token(alice.key.pubkey), + ), + output.Operator.token("||"), + output.Condition.token( + output.SIG.token(alice.key.pubkey), + output.Operator.token("&&"), + output.SIG.token(bob.key.pubkey), + ), + ), + output.Operator.token("||"), + output.Condition.token( + output.SIG.token(bob.key.pubkey), + output.Operator.token("&&"), + output.CSV.token(delay_24h), + ), + ) + + # alice spend the source from tx2 with the password + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + tx2_condition, + [alice.key.pubkey], + [password], + tx2_hash, + ) + assert result is True + assert _errors == [ + ( + "SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7", + ), + ( + "SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7", + ), + ("CSV(86400)", sources.EVALUATE_CONDITION_ERROR_CSV, median_time + delay_24h), + ] + + # the password is revealed in the unlock of the tx3 spending tx2 + # bob can now spend tx1 using the password + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + tx1_condition, + [bob.key.pubkey], + [password], + tx1_hash, + ) + assert result is True + assert _errors == [ + ( + "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo", + ), + ( + "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo", + ), + ("CSV(172800)", "locked by a delay until", median_time + delay_48h), + ] + + # alice and bob can sign together to spend tx1 + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + tx1_condition, + [bob.key.pubkey, alice.key.pubkey], + [password], + tx1_hash, + ) + assert result is True + assert _errors == [ + ("CSV(172800)", sources.EVALUATE_CONDITION_ERROR_CSV, median_time + delay_48h) + ] + + # alice and bob can sign together to spend tx2 + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + tx2_condition, + [bob.key.pubkey, alice.key.pubkey], + [password], + tx2_hash, + ) + assert result is True + assert _errors == [ + ("CSV(86400)", sources.EVALUATE_CONDITION_ERROR_CSV, median_time + delay_24h) + ] + + # alice can not spend the source from tx2 without the password + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + tx2_condition, + [alice.key.pubkey], + [], + tx2_hash, + ) + assert result is False + assert _errors == [ + ( + "XHX(9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08)", + sources.EVALUATE_CONDITION_ERROR_XHX, + "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08", + ), + ( + "SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7", + ), + ( + "SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7", + ), + ("CSV(86400)", "locked by a delay until", median_time + delay_24h), + ] + + # bob can not spend tx1 without the password + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + tx1_condition, + [bob.key.pubkey], + [], + tx1_hash, + ) + assert result is False + assert _errors == [ + ( + "XHX(9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08)", + sources.EVALUATE_CONDITION_ERROR_XHX, + "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08", + ), + ( + "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo", + ), + ( + "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo", + ), + ("CSV(172800)", sources.EVALUATE_CONDITION_ERROR_CSV, median_time + delay_48h), + ] + + # TX1 Transaction was made 48h ago... + # update TX1 transaction in alice tx history + transactions_repo.update( + Transaction( + "testcurrency", + alice.key.pubkey, # alice history + tx1_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time - delay_48h, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, # do not care + bob.key.pubkey, # do not care + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + # update TX1 transaction in bob tx history + transactions_repo.update( + Transaction( + "testcurrency", + bob.key.pubkey, # bob history + tx1_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time - delay_48h, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, # do not care + bob.key.pubkey, # do not care + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + + # TX2 Transaction was made 24h ago... + # update TX2 transaction in alice tx history + transactions_repo.update( + Transaction( + "testcurrency", + alice.key.pubkey, # alice history + tx2_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time - delay_24h, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, # do not care + bob.key.pubkey, # do not care + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + # update TX2 transaction in bob tx history + transactions_repo.update( + Transaction( + "testcurrency", + bob.key.pubkey, # bob history + tx2_hash, + 20, + "15-76543400E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + median_time - delay_24h, + "H41/8OGV2W4CLKbE35kk5t1HJQsb3jEM0/QGLUf80CwJvGZf3HvVCcNtHPUFoUBKEDQO9mPK3KJkqOoxHpqHCw==", + alice.key.pubkey, # do not care + bob.key.pubkey, # do not care + 1565, + 1, + "", + 0, + Transaction.VALIDATED, + ) + ) + + # alice can get back the source from tx1 without the password after 48h + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + tx1_condition, + [alice.key.pubkey], + [], + tx1_hash, + ) + assert result is True + assert _errors == [ + ( + "XHX(9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08)", + sources.EVALUATE_CONDITION_ERROR_XHX, + "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08", + ), + ( + "SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7", + ), + ( + "SIG(GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "GfFUvqaVSgCt6nFDQCAuULWk6K16MUDckeyBJQFcaYj7", + ), + ] + + # bob can spend tx2 without the password after 24h + ( + result, + _errors, + ) = application_with_one_connection.sources_service.evaluate_condition( + application_with_one_connection.currency, + tx2_condition, + [bob.key.pubkey], + [], + tx2_hash, + ) + assert result is True + assert _errors == [ + ( + "XHX(9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08)", + sources.EVALUATE_CONDITION_ERROR_XHX, + "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08", + ), + ( + "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo", + ), + ( + "SIG(F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo)", + sources.EVALUATE_CONDITION_ERROR_SIG, + "F3HWkYnUSbdpEueosKqzYd1m8ftwojwE2uXR7ScoAVKo", + ), + ]