Skip to content

Commit 8d416f6

Browse files
authored
Merge pull request #1090 from KvngMikey/feat/send-locktime-flag
feat(cli): add --timelock/-t option to send command
2 parents 0b174fc + 172fe5f commit 8d416f6

3 files changed

Lines changed: 92 additions & 9 deletions

File tree

cashu/wallet/cli/cli.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,13 @@ async def balance(ctx: Context, verbose):
821821
help="Force swap token.",
822822
type=bool,
823823
)
824+
@click.option(
825+
"--timelock",
826+
"-t",
827+
default=None,
828+
help="Locktime in seconds after which the refund pubkey can claim the tokens.",
829+
type=int,
830+
)
824831
@click.pass_context
825832
@coro
826833
@init_auth_wallet
@@ -835,6 +842,7 @@ async def send_command(
835842
offline: bool,
836843
include_fees: bool,
837844
force_swap: bool,
845+
timelock: Optional[int],
838846
):
839847
wallet: Wallet = ctx.obj["WALLET"]
840848
amount = int(
@@ -850,6 +858,7 @@ async def send_command(
850858
memo=memo,
851859
force_swap=force_swap,
852860
refund_pubkeys=list(refund) if refund else None,
861+
timelock_seconds=timelock,
853862
)
854863
await print_balance(ctx)
855864

@@ -1196,6 +1205,8 @@ async def lock_p2pk(ctx: Context, timelock: Optional[int], refund: tuple):
11961205
print("")
11971206

11981207
send_cmd = f"cashu send <amount> --lock {lock_str}"
1208+
if timelock:
1209+
send_cmd += f" --timelock {timelock}"
11991210
if refund:
12001211
for r in refund:
12011212
send_cmd += f" --refund {r}"

cashu/wallet/helpers.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ async def send(
141141
memo: Optional[str] = None,
142142
force_swap: bool = False,
143143
refund_pubkeys: Optional[List[str]] = None,
144+
timelock_seconds: Optional[int] = None,
144145
):
145146
"""
146147
Prints token to send to stdout.
@@ -151,20 +152,23 @@ async def send(
151152
assert len(lock) > 21, Exception(
152153
"Error: lock has to be at least 22 characters long."
153154
)
154-
# we add a time lock to the P2PK lock by appending the current unix time + 14 days
155+
locktime_seconds = (
156+
timelock_seconds
157+
if timelock_seconds is not None
158+
else settings.locktime_delta_seconds
159+
)
160+
# we add a time lock by appending the current unix time + locktime_seconds
155161
if lock.startswith("P2PK:") or lock.startswith("P2PK-SIGALL:"):
156162
sigall = lock.startswith("P2PK-SIGALL:")
157163
logger.debug(f"Locking token to: {lock}")
158-
logger.debug(
159-
f"Adding a time lock of {settings.locktime_delta_seconds} seconds."
160-
)
164+
logger.debug(f"Adding a time lock of {locktime_seconds} seconds.")
161165
tags = None
162166
if refund_pubkeys:
163167
tags = Tags()
164168
tags["refund"] = refund_pubkeys
165169
secret_lock = await wallet.create_p2pk_lock(
166170
lock.split(":")[1],
167-
locktime_seconds=settings.locktime_delta_seconds,
171+
locktime_seconds=locktime_seconds,
168172
sig_all=sigall,
169173
n_sigs=1,
170174
tags=tags,
@@ -173,16 +177,14 @@ async def send(
173177
elif lock.startswith("P2BK:") or lock.startswith("P2BK-SIGALL:"):
174178
sigall = lock.startswith("P2BK-SIGALL:")
175179
logger.debug(f"Locking token with P2BK to: {lock}")
176-
logger.debug(
177-
f"Adding a time lock of {settings.locktime_delta_seconds} seconds."
178-
)
180+
logger.debug(f"Adding a time lock of {locktime_seconds} seconds.")
179181
tags = None
180182
if refund_pubkeys:
181183
tags = Tags()
182184
tags["refund"] = refund_pubkeys
183185
secret_lock, p2pk_e = await wallet.create_p2bk_lock(
184186
lock.split(":")[1],
185-
locktime_seconds=settings.locktime_delta_seconds,
187+
locktime_seconds=locktime_seconds,
186188
sig_all=sigall,
187189
n_sigs=1,
188190
tags=tags,

tests/wallet/test_wallet_cli.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import asyncio
2+
import time
23
from typing import Tuple
34

45
import bolt11
56
import pytest
67
from click.testing import CliRunner
78

89
from cashu.core.base import TokenV4
10+
from cashu.core.p2pk import P2PKSecret
911
from cashu.core.settings import settings
1012
from cashu.wallet.cli.cli import cli
1113
from cashu.wallet.wallet import Wallet
@@ -687,6 +689,74 @@ def test_send_with_lock_and_refund(mint, cli_prefix):
687689
assert fake_refund_pubkey in token.token[0].proofs[0].secret
688690

689691

692+
def test_send_with_lock_and_timelock(mint, cli_prefix):
693+
runner = CliRunner()
694+
result = runner.invoke(
695+
cli,
696+
[*cli_prefix, "locks"],
697+
)
698+
assert result.exception is None
699+
lock = None
700+
for word in result.output.split(" "):
701+
word = word.strip()
702+
if word.startswith("P2PK:"):
703+
lock = word
704+
break
705+
assert lock is not None, "no lock found"
706+
707+
before = int(time.time())
708+
result = runner.invoke(
709+
cli,
710+
[*cli_prefix, "send", "10", "--lock", lock, "--timelock", "5"],
711+
)
712+
after = int(time.time())
713+
assert result.exception is None
714+
print("test_send_with_lock_and_timelock", result.output)
715+
token_str = result.output.split("\n")[0]
716+
assert "cashuB" in token_str, "output does not have a token"
717+
token = TokenV4.deserialize(token_str).to_tokenv3()
718+
secret = P2PKSecret.deserialize(token.token[0].proofs[0].secret)
719+
assert secret.locktime is not None
720+
assert before + 5 <= secret.locktime <= after + 5
721+
722+
723+
def test_send_with_lock_uses_locktime_delta_seconds_by_default(mint, cli_prefix):
724+
runner = CliRunner()
725+
result = runner.invoke(
726+
cli,
727+
[*cli_prefix, "locks"],
728+
)
729+
assert result.exception is None
730+
lock = None
731+
for word in result.output.split(" "):
732+
word = word.strip()
733+
if word.startswith("P2PK:"):
734+
lock = word
735+
break
736+
assert lock is not None, "no lock found"
737+
738+
before = int(time.time())
739+
result = runner.invoke(
740+
cli,
741+
[*cli_prefix, "send", "10", "--lock", lock],
742+
)
743+
after = int(time.time())
744+
assert result.exception is None
745+
print(
746+
"test_send_with_lock_uses_locktime_delta_seconds_by_default", result.output
747+
)
748+
token_str = result.output.split("\n")[0]
749+
assert "cashuB" in token_str, "output does not have a token"
750+
token = TokenV4.deserialize(token_str).to_tokenv3()
751+
secret = P2PKSecret.deserialize(token.token[0].proofs[0].secret)
752+
assert secret.locktime is not None
753+
assert (
754+
before + settings.locktime_delta_seconds
755+
<= secret.locktime
756+
<= after + settings.locktime_delta_seconds
757+
)
758+
759+
690760
def mint_tokens(runner, cli_prefix, amount: str):
691761
result = runner.invoke(
692762
cli,

0 commit comments

Comments
 (0)