mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
Compare commits
240 commits
v0.33.0-be
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d1dbcb599 | ||
|
|
f826260266 | ||
|
|
e772f8ccfa | ||
|
|
b596eabab3 | ||
|
|
b689e361c1 | ||
|
|
6853e69a05 | ||
|
|
b3776e92f4 | ||
|
|
51eb8f410a | ||
|
|
c75d7e3e81 | ||
|
|
41b884f610 | ||
|
|
2989ceee42 | ||
|
|
806c1f9356 | ||
|
|
7cab89d45b | ||
|
|
aa79fa5262 | ||
|
|
95e184a40f | ||
|
|
1b81a06b42 | ||
|
|
57fb007f95 | ||
|
|
6f0d817db4 | ||
|
|
f5f76a61cc | ||
|
|
4016f4a8f2 | ||
|
|
414760c607 | ||
|
|
a8e3e9c853 | ||
|
|
b686d97445 | ||
|
|
9e97992e18 | ||
|
|
cfbade1239 | ||
|
|
dc39d63d8d | ||
|
|
c7d5e466cd | ||
|
|
514c3f06ad | ||
|
|
8f831e85ae | ||
|
|
2dda482660 | ||
|
|
dede47e727 | ||
|
|
f51148a3a0 | ||
|
|
4acabf72ee | ||
|
|
6b942941fb | ||
|
|
edad26b876 | ||
|
|
4732ec71e9 | ||
|
|
4ca7a9c5ec | ||
|
|
cb97b2a37f | ||
|
|
4161f5d128 | ||
|
|
d80733a710 | ||
|
|
dbf1205b57 | ||
|
|
a463c734ea | ||
|
|
eb63b0b4fe | ||
|
|
41bc5f85df | ||
|
|
2cc2b9cbb6 | ||
|
|
d5fb8b8da8 | ||
|
|
4a2bcb3374 | ||
|
|
e3e6205a4b | ||
|
|
f5cc85f7f3 | ||
|
|
7200a7ce09 | ||
|
|
34cf98e8e8 | ||
|
|
df7dfba12a | ||
|
|
4c03e46928 | ||
|
|
4bb7d5ee29 | ||
|
|
9bccb649f4 | ||
|
|
359997ca7b | ||
|
|
2e80b92cf9 | ||
|
|
9f2d724c3e | ||
|
|
0176e96e3c | ||
|
|
e77a1ab634 | ||
|
|
1d5b1c564a | ||
|
|
378825414b | ||
|
|
8716a51722 | ||
|
|
89f2dcff8b | ||
|
|
74cbb651bc | ||
|
|
338a6ef9f1 | ||
|
|
39c97e60d2 | ||
|
|
43b16ff3e1 | ||
|
|
c718e9a9fb | ||
|
|
6927c67b96 | ||
|
|
332aab79ed | ||
|
|
6593afc8bc | ||
|
|
f01a1f02d9 | ||
|
|
3e2b9452fe | ||
|
|
e53bb67e73 | ||
|
|
491fddbc34 | ||
|
|
b0f43bbe1e | ||
|
|
da96842264 | ||
|
|
ef78c85e88 | ||
|
|
1d935c657f | ||
|
|
dc7da41b28 | ||
|
|
1c89ff83f1 | ||
|
|
0223caa370 | ||
|
|
557ba99513 | ||
|
|
e8dd3aa009 | ||
|
|
e4bcc94a36 | ||
|
|
ac12d251f5 | ||
|
|
1abe617991 | ||
|
|
12ebeabd3d | ||
|
|
aed7df97a0 | ||
|
|
c87273d32e | ||
|
|
814f046d8b | ||
|
|
6582aa0807 | ||
|
|
bd3882d5b0 | ||
|
|
3fdd9e2250 | ||
|
|
67252a84de | ||
|
|
814af6aadf | ||
|
|
0c0cee377b | ||
|
|
58fbe2230e | ||
|
|
f468f24e6f | ||
|
|
d8b24d31c0 | ||
|
|
7f57f6fc6b | ||
|
|
77d9335da2 | ||
|
|
38dce3685f | ||
|
|
6ce13ba8a4 | ||
|
|
f8e9d11d04 | ||
|
|
508bf90a1c | ||
|
|
07d87c23a6 | ||
|
|
8456314155 | ||
|
|
a6d061c568 | ||
|
|
ee5d84b323 | ||
|
|
0611832030 | ||
|
|
ba1c37c0da | ||
|
|
eea3f96ada | ||
|
|
baecbbfe4c | ||
|
|
a134cb1f22 | ||
|
|
22cec7486c | ||
|
|
375c02e0f6 | ||
|
|
682d45405c | ||
|
|
5d8a5019cf | ||
|
|
d324b4bfd8 | ||
|
|
09c92527ea | ||
|
|
a92f6bc875 | ||
|
|
129d9c1d26 | ||
|
|
0bb06b0ba3 | ||
|
|
d0c613e5c9 | ||
|
|
f982ceb753 | ||
|
|
bbeb813bfa | ||
|
|
44a6c7a144 | ||
|
|
b44f8484f2 | ||
|
|
a7fb06e205 | ||
|
|
8087c7aa13 | ||
|
|
914a3eeced | ||
|
|
950b5f1772 | ||
|
|
2d446b3b9e | ||
|
|
cfe9e401c4 | ||
|
|
af6af819cf | ||
|
|
9807c95344 | ||
|
|
f879c52e65 | ||
|
|
63affeefd6 | ||
|
|
14e540fdc6 | ||
|
|
c6b4362e2d | ||
|
|
51f984f7c0 | ||
|
|
5818b986ae | ||
|
|
f443fe4f62 | ||
|
|
33917e1dbd | ||
|
|
6c57f05d8a | ||
|
|
c7ee8f27e9 | ||
|
|
c56c6d3563 | ||
|
|
410a4551fc | ||
|
|
e0e1da5551 | ||
|
|
cc0392af3f | ||
|
|
605e72a261 | ||
|
|
db9bd06629 | ||
|
|
c78988291c | ||
|
|
11bf5ca219 | ||
|
|
089743dacc | ||
|
|
1fa919711f | ||
|
|
aa772faf98 | ||
|
|
92b2004127 | ||
|
|
772e67fb86 | ||
|
|
4b8bdfdbb1 | ||
|
|
12ef083e91 | ||
|
|
859ba4508e | ||
|
|
83534a283c | ||
|
|
7fcc508193 | ||
|
|
8d6df2bf20 | ||
|
|
616b0534a6 | ||
|
|
8ea9adbd35 | ||
|
|
58ebb042ff | ||
|
|
e467ac932b | ||
|
|
7cf0a87c2b | ||
|
|
94fc04a71a | ||
|
|
48a7bdc7d0 | ||
|
|
562416e99b | ||
|
|
ac58336104 | ||
|
|
8d5cc2168a | ||
|
|
72a38c535b | ||
|
|
60d43898f0 | ||
|
|
3abede2d8e | ||
|
|
ca1ce698f0 | ||
|
|
e6e3820a76 | ||
|
|
7683da65ff | ||
|
|
662f954e0f | ||
|
|
541e0e00fc | ||
|
|
074257e348 | ||
|
|
5157dad741 | ||
|
|
9640e3bd4c | ||
|
|
c3009182b3 | ||
|
|
137097915c | ||
|
|
c3e102527a | ||
|
|
d0591e62b9 | ||
|
|
cdec0cacef | ||
|
|
3e9a99dd55 | ||
|
|
74c7274b03 | ||
|
|
3182be8bf1 | ||
|
|
61945bd9f9 | ||
|
|
ea03440f29 | ||
|
|
1b25d8a122 | ||
|
|
707fef340b | ||
|
|
8a4e2f1941 | ||
|
|
b542b50f7f | ||
|
|
010212044a | ||
|
|
de279bca67 | ||
|
|
6aa6979e77 | ||
|
|
38481fe4a9 | ||
|
|
7c3eb29665 | ||
|
|
02dc98232c | ||
|
|
e340797225 | ||
|
|
6ea1c79834 | ||
|
|
38654b91af | ||
|
|
1ec0f77a24 | ||
|
|
8b89f77529 | ||
|
|
62177f9d62 | ||
|
|
807aa61ae6 | ||
|
|
6fd752f7e7 | ||
|
|
7b1c60dba2 | ||
|
|
5b68d40920 | ||
|
|
787519ea7c | ||
|
|
b5d26775a5 | ||
|
|
c8ccc1eba9 | ||
|
|
93669c2b84 | ||
|
|
d295fbc459 | ||
|
|
fc47f8ee3a | ||
|
|
0031ed5eba | ||
|
|
13ded17d9e | ||
|
|
7396653e55 | ||
|
|
51bb2b6ac5 | ||
|
|
e1f1077f44 | ||
|
|
bfbc180e4b | ||
|
|
118c73a0bd | ||
|
|
e8bae4380c | ||
|
|
2f5e821049 | ||
|
|
aa699b16e6 | ||
|
|
bf47c92477 | ||
|
|
d95da382d9 | ||
|
|
f128203ef3 | ||
|
|
b5eeeecd6a | ||
|
|
af79d5480c | ||
|
|
1614375fd9 |
408 changed files with 42532 additions and 3527 deletions
3
.github/pull_request_template.md
vendored
3
.github/pull_request_template.md
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
#### Pull Request Checklist
|
#### Pull Request Checklist
|
||||||
- [ ] Update `release_notes.md` if your PR contains major features, breaking changes or bugfixes
|
- [ ] Add an entry to `docs/release-notes/release-notes-next.md`, or apply the
|
||||||
|
`no-changelog` label (required by CI)
|
||||||
|
|
|
||||||
74
.github/workflows/claude-code-review.yml
vendored
74
.github/workflows/claude-code-review.yml
vendored
|
|
@ -5,8 +5,6 @@ on:
|
||||||
types: [created]
|
types: [created]
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [labeled, synchronize]
|
types: [labeled, synchronize]
|
||||||
pull_request_target:
|
|
||||||
types: [labeled, synchronize]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
claude-review:
|
claude-review:
|
||||||
|
|
@ -55,75 +53,3 @@ jobs:
|
||||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||||
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
|
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
|
||||||
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
|
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
|
||||||
|
|
||||||
claude-approve:
|
|
||||||
if: |
|
|
||||||
github.event_name == 'pull_request_target' &&
|
|
||||||
contains(github.event.pull_request.labels.*.name, 'low-risk') &&
|
|
||||||
(github.event.action == 'labeled' || github.event.action == 'synchronize')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
issues: read
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Run Claude Code Review and Approve
|
|
||||||
id: claude-approve
|
|
||||||
uses: anthropics/claude-code-action@v1
|
|
||||||
with:
|
|
||||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
||||||
prompt: |
|
|
||||||
REPO: ${{ github.repository }}
|
|
||||||
PR NUMBER: ${{ github.event.pull_request.number }}
|
|
||||||
|
|
||||||
Review this pull request thoroughly, checking for:
|
|
||||||
- Code quality and best practices
|
|
||||||
- Potential bugs or issues
|
|
||||||
- Performance considerations
|
|
||||||
- Security concerns
|
|
||||||
- Test coverage
|
|
||||||
|
|
||||||
Use the repository's AGENTS.md for guidance on style and conventions.
|
|
||||||
|
|
||||||
Classify the PR's intrinsic risk severity using one of:
|
|
||||||
- critical: must block merge
|
|
||||||
- high: large blast radius or very sensitive code paths
|
|
||||||
- medium: meaningful production or maintainability risk
|
|
||||||
- low: constrained blast radius and straightforward rollback
|
|
||||||
|
|
||||||
Intrinsic risk is about the impact and sensitivity of changed code,
|
|
||||||
even when no concrete bug is found.
|
|
||||||
Any changes touching fund movement, signing/sweeping, swap state
|
|
||||||
transitions, security/authz/authn logic, secrets handling, or DB
|
|
||||||
schema/migrations are at least medium risk.
|
|
||||||
|
|
||||||
Also classify each finding using one of:
|
|
||||||
- critical
|
|
||||||
- high
|
|
||||||
- medium
|
|
||||||
- low
|
|
||||||
- nit
|
|
||||||
|
|
||||||
Decision rule:
|
|
||||||
- Approve ONLY if intrinsic PR risk is low AND highest finding
|
|
||||||
severity is low or nit.
|
|
||||||
- If intrinsic PR risk is medium/high/critical, DO NOT approve and
|
|
||||||
submit a changes-requested review.
|
|
||||||
- If any finding is medium/high/critical, DO NOT approve and submit
|
|
||||||
a changes-requested review.
|
|
||||||
- If uncertain, treat as medium risk.
|
|
||||||
|
|
||||||
If approving:
|
|
||||||
gh pr review ${{ github.event.pull_request.number }} --approve --body "Claude review: intrinsic PR risk low and findings low/nit; safe for low-risk path."
|
|
||||||
|
|
||||||
If NOT approving:
|
|
||||||
gh pr review ${{ github.event.pull_request.number }} --request-changes --body "Claude review: not eligible for low-risk auto-approval. Include intrinsic PR risk and findings with severities."
|
|
||||||
|
|
||||||
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh pr review:*)"'
|
|
||||||
|
|
|
||||||
66
.github/workflows/gateway.yml
vendored
Normal file
66
.github/workflows/gateway.yml
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
name: gateway
|
||||||
|
|
||||||
|
# Opt-in code-review bot. Triggered by a `/gateway <command>` comment on a PR
|
||||||
|
# (e.g. `/gateway review`); review/approve commands are gated to maintainers.
|
||||||
|
# Comment-commands only — no pull_request triggers — so fork PRs (which receive
|
||||||
|
# no secrets) never spawn failing runs. v0.5.0 added the
|
||||||
|
# pull_request_review_comment trigger: /gateway dismiss, promote, and explain
|
||||||
|
# now also work as replies on a finding's inline thread (finding id inferred
|
||||||
|
# from the thread when omitted). Also a comment event — same fork-PR safety
|
||||||
|
# profile as issue_comment.
|
||||||
|
#
|
||||||
|
# Thin shim: the public lightninglabs/gateway-action mints an App token and
|
||||||
|
# checks out the private gateway runtime at execution time. The runtime stays
|
||||||
|
# private; only this entry point is public.
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
pull_request_review_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
# The action mints an App installation token internally; the GITHUB_TOKEN
|
||||||
|
# handed to this shim is unused, so we minimise it.
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
review:
|
||||||
|
# issue_comment fires for all issues and every PR comment. Filter to PR
|
||||||
|
# comments that look like a /gateway command so unrelated comments don't
|
||||||
|
# spin up a no-op runner. `contains` (not `startsWith`) because the runtime
|
||||||
|
# accepts the command at column 0 of any line, including multi-line bodies.
|
||||||
|
if: >-
|
||||||
|
${{
|
||||||
|
(github.event_name == 'issue_comment'
|
||||||
|
&& github.event.issue.pull_request != null
|
||||||
|
&& contains(github.event.comment.body, '/gateway')) ||
|
||||||
|
(github.event_name == 'pull_request_review_comment'
|
||||||
|
&& contains(github.event.comment.body, '/gateway'))
|
||||||
|
}}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
env:
|
||||||
|
GATEWAY_REVIEW_MODE: multi
|
||||||
|
steps:
|
||||||
|
- uses: lightninglabs/gateway-action@334a8455ee316e40668ae3ac85249150c62704ec # v0.6.0
|
||||||
|
with:
|
||||||
|
# Pin the private runtime to an immutable commit (matches the action
|
||||||
|
# SHA-pin above) so runtime upgrades go through a loop PR, not a moved
|
||||||
|
# tag. Without this, runtime_ref defaults to the v0.6.0 tag.
|
||||||
|
runtime_ref: 75f6e67deac362bdcfc10d10629ddcf69c0e2615 # gateway v0.6.0
|
||||||
|
event_name: ${{ github.event_name }}
|
||||||
|
event_action: ${{ github.event.action }}
|
||||||
|
repo: ${{ github.repository }}
|
||||||
|
pr_number: ${{ github.event.issue.number || github.event.pull_request.number }}
|
||||||
|
actor: ${{ github.event.sender.login }}
|
||||||
|
comment_body: ${{ github.event.comment.body }}
|
||||||
|
comment_id: ${{ github.event.comment.id }}
|
||||||
|
comment_in_reply_to: ${{ github.event.comment.in_reply_to_id }}
|
||||||
|
# installation_id intentionally omitted: as of gateway v0.4.4 the
|
||||||
|
# runtime resolves the App installation covering this repo from
|
||||||
|
# app_id/private_key, so a hardcoded (and easily wrong-org) id is no
|
||||||
|
# longer needed.
|
||||||
|
app_id: ${{ secrets.GATEWAY_APP_ID }}
|
||||||
|
private_key: ${{ secrets.GATEWAY_PRIVATE_KEY }}
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
34
.github/workflows/main.yml
vendored
34
.github/workflows/main.yml
vendored
|
|
@ -24,6 +24,37 @@ env:
|
||||||
LITD_ITEST_BRANCH: master
|
LITD_ITEST_BRANCH: master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
########################
|
||||||
|
# commit message lint
|
||||||
|
########################
|
||||||
|
commit-message:
|
||||||
|
name: Commit Message
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: git checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Lint commit messages
|
||||||
|
env:
|
||||||
|
EVENT_NAME: ${{ github.event_name }}
|
||||||
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||||
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
BEFORE_SHA: ${{ github.event.before }}
|
||||||
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
|
run: |
|
||||||
|
if [ "$EVENT_NAME" = "pull_request" ]; then
|
||||||
|
range="$BASE_SHA..$HEAD_SHA"
|
||||||
|
elif [ "$EVENT_NAME" = "push" ] &&
|
||||||
|
[ "$BEFORE_SHA" != "0000000000000000000000000000000000000000" ]; then
|
||||||
|
range="$BEFORE_SHA..$GITHUB_SHA"
|
||||||
|
else
|
||||||
|
range="$(git rev-parse HEAD^ 2>/dev/null)..HEAD"
|
||||||
|
fi
|
||||||
|
|
||||||
|
make commitmsg-lint range="$range"
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# RPC compile and check
|
# RPC compile and check
|
||||||
########################
|
########################
|
||||||
|
|
@ -124,6 +155,9 @@ jobs:
|
||||||
- name: check
|
- name: check
|
||||||
run: make docs-check
|
run: make docs-check
|
||||||
|
|
||||||
|
- name: check generated FSM diagrams
|
||||||
|
run: make fsm-check
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# run unit-test sqlite3 race
|
# run unit-test sqlite3 race
|
||||||
########################
|
########################
|
||||||
|
|
|
||||||
58
.github/workflows/release-notes.yml
vendored
Normal file
58
.github/workflows/release-notes.yml
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
name: Release Notes
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- synchronize
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-notes:
|
||||||
|
name: Release Notes
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Skip release notes check
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'no-changelog')
|
||||||
|
run: echo "Release notes check skipped by the no-changelog label."
|
||||||
|
|
||||||
|
- name: git checkout
|
||||||
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-changelog') }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check for a release note
|
||||||
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-changelog') }}
|
||||||
|
env:
|
||||||
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||||
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
RELEASE_NOTES_FILE: docs/release-notes/release-notes-next.md
|
||||||
|
run: |
|
||||||
|
if git diff --unified=0 "$BASE_SHA...$HEAD_SHA" -- \
|
||||||
|
"$RELEASE_NOTES_FILE" |
|
||||||
|
awk '
|
||||||
|
/^@@/ { in_hunk = 1; next }
|
||||||
|
in_hunk && /^\+/ {
|
||||||
|
line = substr($0, 2)
|
||||||
|
if (line ~ /[^[:space:]]/) found = 1
|
||||||
|
}
|
||||||
|
END { exit found ? 0 : 1 }
|
||||||
|
'
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "::error file=$RELEASE_NOTES_FILE::Add a release note or apply the no-changelog label."
|
||||||
|
exit 1
|
||||||
|
|
@ -3,7 +3,7 @@ run:
|
||||||
go: "1.26"
|
go: "1.26"
|
||||||
|
|
||||||
# timeout for analysis
|
# timeout for analysis
|
||||||
timeout: 4m
|
timeout: 6m
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
default: all
|
default: all
|
||||||
|
|
@ -55,14 +55,16 @@ linters:
|
||||||
- wsl_v5
|
- wsl_v5
|
||||||
- noinlineerr
|
- noinlineerr
|
||||||
settings:
|
settings:
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- all
|
||||||
|
- -QF*
|
||||||
|
- -ST*
|
||||||
gosec:
|
gosec:
|
||||||
excludes:
|
excludes:
|
||||||
- G402
|
- G402
|
||||||
- G306
|
- G306
|
||||||
- G115
|
- G115
|
||||||
staticcheck:
|
|
||||||
checks:
|
|
||||||
- -SA1019
|
|
||||||
tagliatelle:
|
tagliatelle:
|
||||||
case:
|
case:
|
||||||
rules:
|
rules:
|
||||||
|
|
|
||||||
30
AGENTS.md
30
AGENTS.md
|
|
@ -51,3 +51,33 @@ The project is a client daemon (`loopd`) that connects to a user's `lnd` node an
|
||||||
* **Cryptography:** Extensively uses Taproot and MuSig2 for efficiency, privacy, and complex spending conditions, especially in the `instantout` and `staticaddr` features.
|
* **Cryptography:** Extensively uses Taproot and MuSig2 for efficiency, privacy, and complex spending conditions, especially in the `instantout` and `staticaddr` features.
|
||||||
* **Labeling (`labels/`):** A utility to create and validate labels for swaps, which helps distinguish between user-initiated and automated swaps (e.g., `[reserved]: autoloop-out`).
|
* **Labeling (`labels/`):** A utility to create and validate labels for swaps, which helps distinguish between user-initiated and automated swaps (e.g., `[reserved]: autoloop-out`).
|
||||||
* **Assets (`assets/`):** Contains logic for interacting with `tapd` (Taproot Assets Protocol Daemon), allowing Loop to facilitate swaps involving assets other than Bitcoin.
|
* **Assets (`assets/`):** Contains logic for interacting with `tapd` (Taproot Assets Protocol Daemon), allowing Loop to facilitate swaps involving assets other than Bitcoin.
|
||||||
|
|
||||||
|
**5. Minimum `lnd` Version (`loopd/run.go`):**
|
||||||
|
|
||||||
|
`loopd` enforces a minimum `lnd` version at startup through
|
||||||
|
`LoopMinRequiredLndVersion` in `loopd/run.go` (handed to `lndclient` as
|
||||||
|
`CheckVersion`). This is a hard gate: loopd refuses to start against an older
|
||||||
|
`lnd` node.
|
||||||
|
|
||||||
|
**Maintenance rule:** whenever you start using an `lnd` gRPC method or message
|
||||||
|
field that does not exist in older `lnd`, bump `LoopMinRequiredLndVersion` to the
|
||||||
|
`lnd` release that introduced that API, and record which API drove the bump in the
|
||||||
|
comment above the variable. Pin it to the *real* floor of the APIs the client uses
|
||||||
|
— do **not** just track the `go.mod` dependency. Historically this value only
|
||||||
|
tracked `go.mod` and drifted out of sync with the APIs actually called (it sat at
|
||||||
|
0.17.0 while the client already depended on 0.18.4 APIs). To find the introducing
|
||||||
|
release, grep the field/method across `lnd` version tags, e.g.
|
||||||
|
`git grep <field> <tag> -- <proto-file>`. As of the current floor (**v0.18.4-beta**)
|
||||||
|
the binding dependencies are the asset loop-out fields
|
||||||
|
`routerrpc.SendPaymentRequest.first_hop_custom_records` and
|
||||||
|
`lnrpc.Route.custom_channel_data` (lnd v0.18.4-beta), plus the sweep-batcher fee
|
||||||
|
floor `walletrpc.EstimateFeeResponse.min_relay_fee_sat_per_kw` (lnd v0.18.3-beta).
|
||||||
|
|
||||||
|
**6. Pull Request Release Notes:**
|
||||||
|
|
||||||
|
Every pull request must either add at least one non-empty line to
|
||||||
|
`docs/release-notes/release-notes-next.md` or carry the `no-changelog` label.
|
||||||
|
Before finishing any change intended for a pull request, add a concise entry
|
||||||
|
under the appropriate section (`New Features`, `Breaking Changes`, `Bug Fixes`,
|
||||||
|
or `Maintenance`). For a change that does not warrant a release note, ensure the
|
||||||
|
pull request uses the `no-changelog` label instead. This is enforced by CI.
|
||||||
|
|
|
||||||
67
Makefile
67
Makefile
|
|
@ -15,6 +15,15 @@ GOBUILD := CGO_ENABLED=0 GO111MODULE=on go build -v
|
||||||
GOINSTALL := CGO_ENABLED=0 GO111MODULE=on go install -v
|
GOINSTALL := CGO_ENABLED=0 GO111MODULE=on go install -v
|
||||||
GOMOD := GO111MODULE=on go mod
|
GOMOD := GO111MODULE=on go mod
|
||||||
|
|
||||||
|
# GitLab-backed module sources can reject CI runner traffic. Seed GitLab and
|
||||||
|
# modernc.org modules through the public Go proxy before the direct check.
|
||||||
|
MOD_CHECK_FILES := go.mod go.sum swapserverrpc/go.mod swapserverrpc/go.sum \
|
||||||
|
looprpc/go.mod looprpc/go.sum tools/go.mod tools/go.sum
|
||||||
|
MOD_CHECK_PREFETCH := $(shell awk '($$1 ~ /^(gitlab\.com\/|modernc\.org\/)/) { \
|
||||||
|
version=$$2; sub("/go.mod$$", "", version); \
|
||||||
|
if (version ~ /^v/) print $$1 "@" version; \
|
||||||
|
}' $(MOD_CHECK_FILES) | sort -u)
|
||||||
|
|
||||||
COMMIT := $(shell git describe --abbrev=40 --dirty --tags)
|
COMMIT := $(shell git describe --abbrev=40 --dirty --tags)
|
||||||
COMMIT_HASH := $(shell git rev-parse HEAD)
|
COMMIT_HASH := $(shell git rev-parse HEAD)
|
||||||
DIRTY := $(shell git diff-index --quiet HEAD -- || echo dirty)
|
DIRTY := $(shell git diff-index --quiet HEAD -- || echo dirty)
|
||||||
|
|
@ -156,12 +165,52 @@ mod-tidy:
|
||||||
|
|
||||||
mod-check:
|
mod-check:
|
||||||
@$(call print, "Checking modules.")
|
@$(call print, "Checking modules.")
|
||||||
|
ifneq ($(MOD_CHECK_PREFETCH),)
|
||||||
|
@GOPROXY=https://proxy.golang.org $(GOMOD) download $(MOD_CHECK_PREFETCH)
|
||||||
|
endif
|
||||||
GOPROXY=direct $(GOMOD) tidy
|
GOPROXY=direct $(GOMOD) tidy
|
||||||
cd swapserverrpc/ && GOPROXY=direct $(GOMOD) tidy
|
cd swapserverrpc/ && GOPROXY=direct $(GOMOD) tidy
|
||||||
cd looprpc/ && GOPROXY=direct $(GOMOD) tidy
|
cd looprpc/ && GOPROXY=direct $(GOMOD) tidy
|
||||||
cd tools/ && GOPROXY=direct $(GOMOD) tidy
|
cd tools/ && GOPROXY=direct $(GOMOD) tidy
|
||||||
if test -n "$$(git status --porcelain)"; then echo "Running go mod tidy changes go.mod/go.sum"; git status; git diff; exit 1; fi
|
if test -n "$$(git status --porcelain)"; then echo "Running go mod tidy changes go.mod/go.sum"; git status; git diff; exit 1; fi
|
||||||
|
|
||||||
|
commitmsg-lint:
|
||||||
|
@$(call print, "Linting commit message(s).")
|
||||||
|
@if [ -n "$(range)" ]; then \
|
||||||
|
./scripts/commit_message.py lint --range "$(range)"; \
|
||||||
|
elif [ -n "$(commit)" ]; then \
|
||||||
|
./scripts/commit_message.py lint --commit "$(commit)"; \
|
||||||
|
elif [ -n "$(file)" ]; then \
|
||||||
|
./scripts/commit_message.py lint --file "$(file)"; \
|
||||||
|
else \
|
||||||
|
./scripts/commit_message.py lint --commit HEAD; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
commitmsg-fmt:
|
||||||
|
@$(call print, "Formatting commit message.")
|
||||||
|
@if [ -n "$(file)" ]; then \
|
||||||
|
if [ "$(inplace)" = "1" ]; then \
|
||||||
|
./scripts/commit_message.py fmt --file "$(file)" --in-place \
|
||||||
|
$(if $(filter 1,$(decode)),--decode-escaped-newlines,); \
|
||||||
|
else \
|
||||||
|
./scripts/commit_message.py fmt --file "$(file)" \
|
||||||
|
$(if $(filter 1,$(decode)),--decode-escaped-newlines,); \
|
||||||
|
fi; \
|
||||||
|
elif [ -n "$(commit)" ]; then \
|
||||||
|
./scripts/commit_message.py fmt --commit "$(commit)" \
|
||||||
|
$(if $(filter 1,$(decode)),--decode-escaped-newlines,); \
|
||||||
|
else \
|
||||||
|
echo "Error: provide file=<path> or commit=<rev>"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
commitmsg-reword:
|
||||||
|
@$(call print, "Rewording commit with formatted message.")
|
||||||
|
@./scripts/commit_message.py reword \
|
||||||
|
--commit "$(if $(commit),$(commit),HEAD)" \
|
||||||
|
$(if $(filter 1,$(decode)),--decode-escaped-newlines,) \
|
||||||
|
$(if $(filter 1,$(dryrun)),--dry-run,)
|
||||||
|
|
||||||
sqlc:
|
sqlc:
|
||||||
@$(call print, "Generating sql models and queries in Go")
|
@$(call print, "Generating sql models and queries in Go")
|
||||||
./scripts/gen_sqlc_docker.sh
|
./scripts/gen_sqlc_docker.sh
|
||||||
|
|
@ -182,4 +231,20 @@ docs-check: docs
|
||||||
fsm:
|
fsm:
|
||||||
@$(call print, "Generating state machine docs")
|
@$(call print, "Generating state machine docs")
|
||||||
./scripts/fsm-generate.sh;
|
./scripts/fsm-generate.sh;
|
||||||
.PHONY: fsm
|
|
||||||
|
FSM_FILES := \
|
||||||
|
fsm/example_fsm.md \
|
||||||
|
instantout/fsm.md \
|
||||||
|
instantout/reservation/fsm.md \
|
||||||
|
staticaddr/deposit/fsm.md \
|
||||||
|
staticaddr/loopin/fsm.md
|
||||||
|
|
||||||
|
fsm-check: fsm
|
||||||
|
@$(call print, "Verifying generated state machine docs")
|
||||||
|
if test -n "$$(git status --porcelain -- $(FSM_FILES))"; then \
|
||||||
|
echo "Generated FSM diagrams are not up-to-date!"; \
|
||||||
|
git status --porcelain -- $(FSM_FILES); \
|
||||||
|
git diff -- $(FSM_FILES); \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
.PHONY: fsm fsm-check
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@ loopd --network=testnet
|
||||||
### Submit feature requests
|
### Submit feature requests
|
||||||
The [GitHub issue tracker](https://github.com/lightninglabs/loop/issues) can be
|
The [GitHub issue tracker](https://github.com/lightninglabs/loop/issues) can be
|
||||||
used to request specific improvements or report bugs.
|
used to request specific improvements or report bugs.
|
||||||
|
Do not use public issues for security disclosures.
|
||||||
|
Follow the instructions in [SECURITY.md](./SECURITY.md).
|
||||||
|
|
||||||
### Join us on Slack
|
### Join us on Slack
|
||||||
Join us on the
|
Join us on the
|
||||||
|
|
@ -131,4 +133,4 @@ go install ./...
|
||||||
## Reproducible builds
|
## Reproducible builds
|
||||||
|
|
||||||
If you want to build release files yourself, follow
|
If you want to build release files yourself, follow
|
||||||
[the guide](./docs/release.md).
|
[the guide](./docs/reproducible_release.md).
|
||||||
|
|
|
||||||
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you believe you have found a security or privacy issue in Lightning Loop,
|
||||||
|
please do not open a public GitHub issue.
|
||||||
|
|
||||||
|
Instead, disclose it responsibly by following the instructions in the
|
||||||
|
[lnd security policy](https://github.com/lightningnetwork/lnd/blob/master/SECURITY.md).
|
||||||
132
assets/client.go
132
assets/client.go
|
|
@ -4,14 +4,15 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/lightninglabs/taproot-assets/rfqmath"
|
"github.com/lightninglabs/taproot-assets/rfqmath"
|
||||||
"github.com/lightninglabs/taproot-assets/rpcutils"
|
|
||||||
"github.com/lightninglabs/taproot-assets/tapcfg"
|
|
||||||
"github.com/lightninglabs/taproot-assets/taprpc"
|
"github.com/lightninglabs/taproot-assets/taprpc"
|
||||||
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
|
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
|
||||||
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
|
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
|
||||||
|
|
@ -49,13 +50,24 @@ type TapdConfig struct {
|
||||||
// DefaultTapdConfig returns a default configuration to connect to a taproot
|
// DefaultTapdConfig returns a default configuration to connect to a taproot
|
||||||
// assets daemon.
|
// assets daemon.
|
||||||
func DefaultTapdConfig() *TapdConfig {
|
func DefaultTapdConfig() *TapdConfig {
|
||||||
defaultConf := tapcfg.DefaultConfig()
|
return DefaultTapdConfigForNetwork(
|
||||||
|
btcutil.AppDataDir("tapd", false), "mainnet",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTapdConfigForNetwork returns the default tapd configuration rooted in
|
||||||
|
// defaultTapdDir for the given Bitcoin network. Passing the directory
|
||||||
|
// explicitly keeps path construction testable without accessing the user's
|
||||||
|
// real tapd data.
|
||||||
|
func DefaultTapdConfigForNetwork(defaultTapdDir, network string) *TapdConfig {
|
||||||
return &TapdConfig{
|
return &TapdConfig{
|
||||||
Activate: false,
|
Activate: false,
|
||||||
Host: "localhost:10029",
|
Host: "localhost:10029",
|
||||||
MacaroonPath: defaultConf.RpcConf.MacaroonPath,
|
MacaroonPath: filepath.Join(
|
||||||
TLSPath: defaultConf.RpcConf.TLSCertPath,
|
defaultTapdDir, "data", network, "admin.macaroon",
|
||||||
RFQtimeout: defaultRfqTimeout,
|
),
|
||||||
|
TLSPath: filepath.Join(defaultTapdDir, "tls.cert"),
|
||||||
|
RFQtimeout: defaultRfqTimeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,14 +79,19 @@ type TapdClient struct {
|
||||||
rfqrpc.RfqClient
|
rfqrpc.RfqClient
|
||||||
universerpc.UniverseClient
|
universerpc.UniverseClient
|
||||||
|
|
||||||
cfg *TapdConfig
|
rfqTimeoutSeconds uint32
|
||||||
assetNameCache map[string]string
|
assetNameCache map[string]string
|
||||||
assetNameMutex sync.Mutex
|
assetNameMutex sync.RWMutex
|
||||||
cc *grpc.ClientConn
|
cc *grpc.ClientConn
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTapdClient returns a new taproot assets client.
|
// NewTapdClient returns a new taproot assets client.
|
||||||
func NewTapdClient(config *TapdConfig) (*TapdClient, error) {
|
func NewTapdClient(config *TapdConfig) (*TapdClient, error) {
|
||||||
|
rfqTimeoutSeconds, err := getRfqTimeoutSeconds(config.RFQtimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Create the client connection to the server.
|
// Create the client connection to the server.
|
||||||
conn, err := getClientConn(config)
|
conn, err := getClientConn(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -85,7 +102,7 @@ func NewTapdClient(config *TapdConfig) (*TapdClient, error) {
|
||||||
client := &TapdClient{
|
client := &TapdClient{
|
||||||
assetNameCache: make(map[string]string),
|
assetNameCache: make(map[string]string),
|
||||||
cc: conn,
|
cc: conn,
|
||||||
cfg: config,
|
rfqTimeoutSeconds: rfqTimeoutSeconds,
|
||||||
TaprootAssetsClient: taprpc.NewTaprootAssetsClient(conn),
|
TaprootAssetsClient: taprpc.NewTaprootAssetsClient(conn),
|
||||||
TaprootAssetChannelsClient: tapchannelrpc.NewTaprootAssetChannelsClient(conn),
|
TaprootAssetChannelsClient: tapchannelrpc.NewTaprootAssetChannelsClient(conn),
|
||||||
PriceOracleClient: priceoraclerpc.NewPriceOracleClient(conn),
|
PriceOracleClient: priceoraclerpc.NewPriceOracleClient(conn),
|
||||||
|
|
@ -128,7 +145,7 @@ func (c *TapdClient) GetRfqForAsset(ctx context.Context,
|
||||||
PeerPubKey: peerPubkey,
|
PeerPubKey: peerPubkey,
|
||||||
PaymentMaxAmt: uint64(paymentMaxAmt),
|
PaymentMaxAmt: uint64(paymentMaxAmt),
|
||||||
Expiry: uint64(expiry),
|
Expiry: uint64(expiry),
|
||||||
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
|
TimeoutSeconds: c.rfqTimeoutSeconds,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -141,21 +158,26 @@ func (c *TapdClient) GetRfqForAsset(ctx context.Context,
|
||||||
rfq.GetRejectedQuote())
|
rfq.GetRejectedQuote())
|
||||||
}
|
}
|
||||||
|
|
||||||
if rfq.GetAcceptedQuote() != nil {
|
acceptedQuote := rfq.GetAcceptedQuote()
|
||||||
return rfq.GetAcceptedQuote(), nil
|
if acceptedQuote == nil {
|
||||||
|
return nil, fmt.Errorf("no accepted quote")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no accepted quote")
|
_, err = unmarshalAssetRate(acceptedQuote.BidAssetRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid accepted quote asset rate: %w",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acceptedQuote, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssetName returns the human-readable name of the asset.
|
// GetAssetName returns the human-readable name of the asset.
|
||||||
func (c *TapdClient) GetAssetName(ctx context.Context,
|
func (c *TapdClient) GetAssetName(ctx context.Context,
|
||||||
assetId []byte) (string, error) {
|
assetId []byte) (string, error) {
|
||||||
|
|
||||||
c.assetNameMutex.Lock()
|
|
||||||
defer c.assetNameMutex.Unlock()
|
|
||||||
assetIdStr := hex.EncodeToString(assetId)
|
assetIdStr := hex.EncodeToString(assetId)
|
||||||
if name, ok := c.assetNameCache[assetIdStr]; ok {
|
if name, ok := c.getCachedAssetName(assetIdStr); ok {
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,11 +203,28 @@ func (c *TapdClient) GetAssetName(ctx context.Context,
|
||||||
assetName = assetStats.AssetStats[0].Asset.AssetName
|
assetName = assetStats.AssetStats[0].Asset.AssetName
|
||||||
}
|
}
|
||||||
|
|
||||||
c.assetNameCache[assetIdStr] = assetName
|
c.cacheAssetName(assetIdStr, assetName)
|
||||||
|
|
||||||
return assetName, nil
|
return assetName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getCachedAssetName returns an asset name from the cache.
|
||||||
|
func (c *TapdClient) getCachedAssetName(assetID string) (string, bool) {
|
||||||
|
c.assetNameMutex.RLock()
|
||||||
|
defer c.assetNameMutex.RUnlock()
|
||||||
|
|
||||||
|
name, ok := c.assetNameCache[assetID]
|
||||||
|
return name, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheAssetName adds an asset name to the cache.
|
||||||
|
func (c *TapdClient) cacheAssetName(assetID, name string) {
|
||||||
|
c.assetNameMutex.Lock()
|
||||||
|
defer c.assetNameMutex.Unlock()
|
||||||
|
|
||||||
|
c.assetNameCache[assetID] = name
|
||||||
|
}
|
||||||
|
|
||||||
// GetAssetPrice returns the price of an asset in satoshis. NOTE: this currently
|
// GetAssetPrice returns the price of an asset in satoshis. NOTE: this currently
|
||||||
// uses the rfq process for the asset price. A future implementation should
|
// uses the rfq process for the asset price. A future implementation should
|
||||||
// use a price oracle to not spam a peer.
|
// use a price oracle to not spam a peer.
|
||||||
|
|
@ -209,7 +248,7 @@ func (c *TapdClient) GetAssetPrice(ctx context.Context, assetID string,
|
||||||
},
|
},
|
||||||
PaymentMaxAmt: uint64(msatAmt),
|
PaymentMaxAmt: uint64(msatAmt),
|
||||||
Expiry: uint64(rfqExpiry),
|
Expiry: uint64(rfqExpiry),
|
||||||
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
|
TimeoutSeconds: c.rfqTimeoutSeconds,
|
||||||
PeerPubKey: peerPubkey,
|
PeerPubKey: peerPubkey,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -243,7 +282,7 @@ func (c *TapdClient) GetAssetPrice(ctx context.Context, assetID string,
|
||||||
func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) (
|
func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) (
|
||||||
btcutil.Amount, error) {
|
btcutil.Amount, error) {
|
||||||
|
|
||||||
rateFP, err := rpcutils.UnmarshalRfqFixedPoint(assetRate)
|
rateFP, err := unmarshalAssetRate(assetRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err)
|
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -255,6 +294,33 @@ func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) (
|
||||||
return msatAmt.ToSatoshis(), nil
|
return msatAmt.ToSatoshis(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unmarshalAssetRate validates and converts an RPC asset rate to the fixed
|
||||||
|
// point representation used for RFQ arithmetic.
|
||||||
|
func unmarshalAssetRate(assetRate *rfqrpc.FixedPoint) (
|
||||||
|
*rfqmath.BigIntFixedPoint, error) {
|
||||||
|
|
||||||
|
if assetRate == nil {
|
||||||
|
return nil, fmt.Errorf("asset rate cannot be nil")
|
||||||
|
}
|
||||||
|
if assetRate.Scale > math.MaxUint8 {
|
||||||
|
return nil, fmt.Errorf("scale value overflow: %v", assetRate.Scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
coefficient, ok := new(big.Int).SetString(assetRate.Coefficient, 10)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid asset rate coefficient: %q",
|
||||||
|
assetRate.Coefficient)
|
||||||
|
}
|
||||||
|
if coefficient.Sign() <= 0 {
|
||||||
|
return nil, fmt.Errorf("asset rate coefficient must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rfqmath.BigIntFixedPoint{
|
||||||
|
Coefficient: rfqmath.NewBigInt(coefficient),
|
||||||
|
Scale: uint8(assetRate.Scale),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// getPaymentMaxAmount returns the milisat amount we are willing to pay for the
|
// getPaymentMaxAmount returns the milisat amount we are willing to pay for the
|
||||||
// payment.
|
// payment.
|
||||||
func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) (
|
func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) (
|
||||||
|
|
@ -277,6 +343,26 @@ func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) (
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRfqTimeoutSeconds converts the configured RFQ timeout to the whole
|
||||||
|
// seconds accepted by tapd. Fractional seconds are rounded up so tapd's
|
||||||
|
// timeout is never shorter than the configured duration.
|
||||||
|
func getRfqTimeoutSeconds(timeout time.Duration) (uint32, error) {
|
||||||
|
if timeout <= 0 {
|
||||||
|
return 0, fmt.Errorf("RFQ timeout must be greater than zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
seconds := timeout / time.Second
|
||||||
|
if timeout%time.Second != 0 {
|
||||||
|
seconds++
|
||||||
|
}
|
||||||
|
if seconds > time.Duration(math.MaxUint32) {
|
||||||
|
return 0, fmt.Errorf("RFQ timeout exceeds maximum of %v seconds",
|
||||||
|
uint64(math.MaxUint32))
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint32(seconds), nil
|
||||||
|
}
|
||||||
|
|
||||||
func getClientConn(config *TapdConfig) (*grpc.ClientConn, error) {
|
func getClientConn(config *TapdConfig) (*grpc.ClientConn, error) {
|
||||||
// Load the specified TLS certificate and build transport credentials.
|
// Load the specified TLS certificate and build transport credentials.
|
||||||
creds, err := credentials.NewClientTLSFromFile(config.TLSPath, "")
|
creds, err := credentials.NewClientTLSFromFile(config.TLSPath, "")
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,269 @@
|
||||||
package assets
|
package assets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
|
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
|
||||||
|
"github.com/lightninglabs/taproot-assets/taprpc/universerpc"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"gopkg.in/macaroon.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type blockingUniverseClient struct {
|
||||||
|
universerpc.UniverseClient
|
||||||
|
|
||||||
|
queryStarted chan struct{}
|
||||||
|
releaseQuery chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blockingUniverseClient) QueryAssetStats(context.Context,
|
||||||
|
*universerpc.AssetStatsQuery, ...grpc.CallOption) (
|
||||||
|
*universerpc.UniverseAssetStats, error) {
|
||||||
|
|
||||||
|
close(b.queryStarted)
|
||||||
|
<-b.releaseQuery
|
||||||
|
|
||||||
|
return &universerpc.UniverseAssetStats{
|
||||||
|
AssetStats: []*universerpc.AssetStatsSnapshot{
|
||||||
|
{
|
||||||
|
Asset: &universerpc.AssetStatsAsset{
|
||||||
|
AssetName: "queried asset",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type staticRfqClient struct {
|
||||||
|
rfqrpc.RfqClient
|
||||||
|
|
||||||
|
response *rfqrpc.AddAssetSellOrderResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *staticRfqClient) AddAssetSellOrder(context.Context,
|
||||||
|
*rfqrpc.AddAssetSellOrderRequest, ...grpc.CallOption) (
|
||||||
|
*rfqrpc.AddAssetSellOrderResponse, error) {
|
||||||
|
|
||||||
|
return s.response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDefaultTapdConfig tests that the default tapd connection paths match
|
||||||
|
// tapd's mainnet defaults.
|
||||||
|
func TestDefaultTapdConfig(t *testing.T) {
|
||||||
|
defaultTapdDir := btcutil.AppDataDir("tapd", false)
|
||||||
|
config := DefaultTapdConfig()
|
||||||
|
|
||||||
|
require.Equal(t, filepath.Join(
|
||||||
|
defaultTapdDir, "data", "mainnet", "admin.macaroon",
|
||||||
|
), config.MacaroonPath)
|
||||||
|
require.Equal(
|
||||||
|
t, filepath.Join(defaultTapdDir, "tls.cert"), config.TLSPath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTapdConfigClientConn tests that the default tapd file layout can be used
|
||||||
|
// to construct a client connection.
|
||||||
|
func TestTapdConfigClientConn(t *testing.T) {
|
||||||
|
// Use an isolated tapd root so the test never reads from or writes to a
|
||||||
|
// user's real tapd data directory.
|
||||||
|
defaultTapdDir := t.TempDir()
|
||||||
|
network := "regtest"
|
||||||
|
macaroonPath := filepath.Join(
|
||||||
|
defaultTapdDir, "data", network, "admin.macaroon",
|
||||||
|
)
|
||||||
|
require.NoError(t, os.MkdirAll(filepath.Dir(macaroonPath), 0o700))
|
||||||
|
|
||||||
|
// NewTapdClient parses the configured TLS certificate before creating
|
||||||
|
// its gRPC client. An httptest server provides a valid certificate
|
||||||
|
// without requiring a running tapd instance.
|
||||||
|
tlsServer := httptest.NewTLSServer(http.NotFoundHandler())
|
||||||
|
t.Cleanup(tlsServer.Close)
|
||||||
|
cert := tlsServer.Certificate()
|
||||||
|
certBytes := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE", Bytes: cert.Raw,
|
||||||
|
})
|
||||||
|
require.NoError(t, os.WriteFile(
|
||||||
|
filepath.Join(defaultTapdDir, "tls.cert"), certBytes, 0o600,
|
||||||
|
))
|
||||||
|
|
||||||
|
// Store a valid serialized macaroon at tapd's production path. This
|
||||||
|
// ensures connection setup tests the path itself rather than failing on
|
||||||
|
// malformed credentials.
|
||||||
|
mac, err := macaroon.New(
|
||||||
|
[]byte("root-key"), []byte("id"), "tapd",
|
||||||
|
macaroon.LatestVersion,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
macBytes, err := mac.MarshalBinary()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, os.WriteFile(macaroonPath, macBytes, 0o600))
|
||||||
|
|
||||||
|
// grpc.NewClient connects lazily, so constructing the tapd client verifies
|
||||||
|
// that both credentials can be loaded and parsed without needing a live
|
||||||
|
// tapd server.
|
||||||
|
config := DefaultTapdConfigForNetwork(defaultTapdDir, network)
|
||||||
|
client, err := NewTapdClient(config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, client.cc.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Equal(t, macaroonPath, config.MacaroonPath)
|
||||||
|
require.Equal(
|
||||||
|
t, filepath.Join(defaultTapdDir, "tls.cert"), config.TLSPath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetAssetNameCachedLookupNotBlocked verifies that a slow universe query
|
||||||
|
// for one asset does not prevent another caller from reading a cached name.
|
||||||
|
func TestGetAssetNameCachedLookupNotBlocked(t *testing.T) {
|
||||||
|
const cachedName = "cached asset"
|
||||||
|
|
||||||
|
cachedAssetID := []byte{1}
|
||||||
|
queryStarted := make(chan struct{})
|
||||||
|
releaseQuery := make(chan struct{})
|
||||||
|
client := &TapdClient{
|
||||||
|
UniverseClient: &blockingUniverseClient{
|
||||||
|
queryStarted: queryStarted,
|
||||||
|
releaseQuery: releaseQuery,
|
||||||
|
},
|
||||||
|
assetNameCache: map[string]string{
|
||||||
|
hex.EncodeToString(cachedAssetID): cachedName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
queryResult := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
_, err := client.GetAssetName(context.Background(), []byte{2})
|
||||||
|
queryResult <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-queryStarted:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("universe query did not start")
|
||||||
|
}
|
||||||
|
|
||||||
|
type nameResult struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
cachedResult := make(chan nameResult, 1)
|
||||||
|
go func() {
|
||||||
|
name, err := client.GetAssetName(
|
||||||
|
context.Background(), cachedAssetID,
|
||||||
|
)
|
||||||
|
cachedResult <- nameResult{name: name, err: err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-cachedResult:
|
||||||
|
require.NoError(t, result.err)
|
||||||
|
require.Equal(t, cachedName, result.name)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
close(releaseQuery)
|
||||||
|
t.Fatal("cached lookup blocked behind universe query")
|
||||||
|
}
|
||||||
|
|
||||||
|
close(releaseQuery)
|
||||||
|
require.NoError(t, <-queryResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetRfqForAssetValidatesRate verifies that malformed accepted quote rates
|
||||||
|
// are rejected before they reach downstream RFQ arithmetic.
|
||||||
|
func TestGetRfqForAssetValidatesRate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
assetRate *rfqrpc.FixedPoint
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "100000", Scale: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
assetRate: nil,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed coefficient",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "not-a-number", Scale: 0,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero coefficient",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "0", Scale: 0,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative coefficient",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "-1", Scale: 0,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scale overflow",
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "1", Scale: 256,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
acceptedQuote := &rfqrpc.PeerAcceptedSellQuote{
|
||||||
|
BidAssetRate: test.assetRate,
|
||||||
|
}
|
||||||
|
acceptedResponse :=
|
||||||
|
&rfqrpc.AddAssetSellOrderResponse_AcceptedQuote{
|
||||||
|
AcceptedQuote: acceptedQuote,
|
||||||
|
}
|
||||||
|
client := &TapdClient{
|
||||||
|
RfqClient: &staticRfqClient{
|
||||||
|
response: &rfqrpc.AddAssetSellOrderResponse{
|
||||||
|
Response: acceptedResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rfqTimeoutSeconds: 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
quote, err := client.GetRfqForAsset(
|
||||||
|
context.Background(), 1000, []byte{1}, []byte{2},
|
||||||
|
time.Now().Add(time.Minute).Unix(), 1,
|
||||||
|
)
|
||||||
|
if test.expectError {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, quote)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Same(t, acceptedQuote, quote)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetPaymentMaxAmount(t *testing.T) {
|
func TestGetPaymentMaxAmount(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
satAmount btcutil.Amount
|
satAmount btcutil.Amount
|
||||||
|
|
@ -68,6 +323,62 @@ func TestGetPaymentMaxAmount(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGetRfqTimeoutSeconds verifies that configured durations are safely
|
||||||
|
// converted to tapd's whole-second timeout field.
|
||||||
|
func TestGetRfqTimeoutSeconds(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
timeout time.Duration
|
||||||
|
expectedSeconds uint32
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "whole seconds",
|
||||||
|
timeout: 60 * time.Second,
|
||||||
|
expectedSeconds: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sub-second rounded up",
|
||||||
|
timeout: time.Millisecond,
|
||||||
|
expectedSeconds: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fractional second rounded up",
|
||||||
|
timeout: time.Second + time.Nanosecond,
|
||||||
|
expectedSeconds: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero",
|
||||||
|
timeout: 0,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative",
|
||||||
|
timeout: -time.Second,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overflow",
|
||||||
|
timeout: time.Duration(math.MaxUint32)*time.Second +
|
||||||
|
time.Nanosecond,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
seconds, err := getRfqTimeoutSeconds(test.timeout)
|
||||||
|
if test.expectError {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.expectedSeconds, seconds)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetSatsFromAssetAmt(t *testing.T) {
|
func TestGetSatsFromAssetAmt(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
assetAmt uint64
|
assetAmt uint64
|
||||||
|
|
@ -93,6 +404,39 @@ func TestGetSatsFromAssetAmt(t *testing.T) {
|
||||||
expected: btcutil.Amount(0),
|
expected: btcutil.Amount(0),
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
assetAmt: 1000,
|
||||||
|
assetRate: nil,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetAmt: 1000,
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "not-a-number", Scale: 0,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetAmt: 1000,
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "0", Scale: 0,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetAmt: 1000,
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "-1", Scale: 0,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assetAmt: 1000,
|
||||||
|
assetRate: &rfqrpc.FixedPoint{
|
||||||
|
Coefficient: "1", Scale: 256,
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
|
||||||
|
|
@ -43,16 +43,10 @@ func forceAutoloop(ctx context.Context, cmd *cli.Command) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDebugClient(ctx context.Context, cmd *cli.Command) (looprpc.DebugClient, func(), error) {
|
func getDebugClient(ctx context.Context, cmd *cli.Command) (looprpc.DebugClient, func(), error) {
|
||||||
rpcServer := cmd.String("rpcserver")
|
conn, cleanup, err := sessionTransport.Dial(cmd)
|
||||||
tlsCertPath, macaroonPath, err := extractPathArgs(cmd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
cleanup := func() { conn.Close() }
|
|
||||||
|
|
||||||
debugClient := looprpc.NewDebugClient(conn)
|
debugClient := looprpc.NewDebugClient(conn)
|
||||||
return debugClient, cleanup, nil
|
return debugClient, cleanup, nil
|
||||||
|
|
|
||||||
82
cmd/loop/default_path_text_test.go
Normal file
82
cmd/loop/default_path_text_test.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDefaultPathText verifies HOME-based path elision behavior used for help
|
||||||
|
// defaults without relying on the real environment.
|
||||||
|
func TestDefaultPathText(t *testing.T) {
|
||||||
|
sep := string(filepath.Separator)
|
||||||
|
home := filepath.Clean(sep + "home" + sep + "alice")
|
||||||
|
|
||||||
|
homeDir := func() (string, error) { return home, nil }
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
homeFn func() (string, error)
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty value",
|
||||||
|
value: "",
|
||||||
|
homeFn: func() (string, error) {
|
||||||
|
return home, nil
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil homedir func",
|
||||||
|
value: home + sep + "data",
|
||||||
|
homeFn: nil,
|
||||||
|
want: home + sep + "data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "homedir error",
|
||||||
|
value: home + sep + "data",
|
||||||
|
homeFn: func() (string, error) {
|
||||||
|
return "", errors.New("homedir error")
|
||||||
|
},
|
||||||
|
want: home + sep + "data",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact home",
|
||||||
|
value: home,
|
||||||
|
homeFn: homeDir,
|
||||||
|
want: "~",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "home prefix",
|
||||||
|
value: home + sep + "dir" + sep + "file",
|
||||||
|
homeFn: homeDir,
|
||||||
|
want: "~" + sep + "dir" + sep + "file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-home path",
|
||||||
|
value: filepath.Clean(sep + "var" + sep + "tmp"),
|
||||||
|
homeFn: homeDir,
|
||||||
|
want: filepath.Clean(sep + "var" + sep + "tmp"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prefix but not path segment",
|
||||||
|
value: home + "x" + sep + "dir",
|
||||||
|
homeFn: homeDir,
|
||||||
|
want: home + "x" + sep + "dir",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
got := defaultPathText(test.value, test.homeFn)
|
||||||
|
require.Equalf(
|
||||||
|
t, test.want, got,
|
||||||
|
"defaultPathText(%q)", test.value,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -88,6 +88,12 @@ func instantOut(ctx context.Context, cmd *cli.Command) error {
|
||||||
fmt.Printf("Available reservations: \n\n")
|
fmt.Printf("Available reservations: \n\n")
|
||||||
for _, res := range confirmedReservations {
|
for _, res := range confirmedReservations {
|
||||||
idx++
|
idx++
|
||||||
|
if len(res.ReservationId) != reservation.IdLength {
|
||||||
|
return fmt.Errorf("invalid reservation id length: "+
|
||||||
|
"got %d, expected %d", len(res.ReservationId),
|
||||||
|
reservation.IdLength)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("Reservation %v: shortid %x, amt %v, expiry "+
|
fmt.Printf("Reservation %v: shortid %x, amt %v, expiry "+
|
||||||
"height %v \n", idx, res.ReservationId[:3], res.Amount,
|
"height %v \n", idx, res.ReservationId[:3], res.Amount,
|
||||||
res.Expiry)
|
res.Expiry)
|
||||||
|
|
@ -131,7 +137,7 @@ func instantOut(ctx context.Context, cmd *cli.Command) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if idx < 0 {
|
if idx <= 0 {
|
||||||
return fmt.Errorf("invalid index %v", idx)
|
return fmt.Errorf("invalid index %v", idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,6 +183,8 @@ func instantOut(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
|
||||||
fmt.Println("Starting instant swap out")
|
fmt.Println("Starting instant swap out")
|
||||||
|
|
||||||
|
maxSwapFee := quote.ServiceFeeSat
|
||||||
|
|
||||||
// Now we can request the instant out swap.
|
// Now we can request the instant out swap.
|
||||||
instantOutRes, err := client.InstantOut(
|
instantOutRes, err := client.InstantOut(
|
||||||
ctx,
|
ctx,
|
||||||
|
|
@ -184,6 +192,9 @@ func instantOut(ctx context.Context, cmd *cli.Command) error {
|
||||||
ReservationIds: selectedReservations,
|
ReservationIds: selectedReservations,
|
||||||
OutgoingChanSet: outgoingChanSet,
|
OutgoingChanSet: outgoingChanSet,
|
||||||
DestAddr: cmd.String("addr"),
|
DestAddr: cmd.String("addr"),
|
||||||
|
MaxSwapFee: &looprpc.InstantOutRequest_MaxSwapFeeSat{
|
||||||
|
MaxSwapFeeSat: maxSwapFee,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,12 @@ var setParamsCommand = &cli.Command{
|
||||||
Usage: "the confirmation target for loop in on-chain " +
|
Usage: "the confirmation target for loop in on-chain " +
|
||||||
"htlcs.",
|
"htlcs.",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "loopinsource",
|
||||||
|
Usage: "the loop-in source to use for autoloop rules: " +
|
||||||
|
"wallet or static-address. Static-address " +
|
||||||
|
"requires loopd --experimental.",
|
||||||
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "easyautoloop",
|
Name: "easyautoloop",
|
||||||
Usage: "set to true to enable easy autoloop, which " +
|
Usage: "set to true to enable easy autoloop, which " +
|
||||||
|
|
@ -555,6 +561,25 @@ func setParams(ctx context.Context, cmd *cli.Command) error {
|
||||||
flagSet = true
|
flagSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cmd.IsSet("loopinsource") {
|
||||||
|
switch cmd.String("loopinsource") {
|
||||||
|
case "wallet":
|
||||||
|
params.LoopInSource =
|
||||||
|
looprpc.LoopInSource_LOOP_IN_SOURCE_WALLET
|
||||||
|
|
||||||
|
case "static-address", "static_address", "static":
|
||||||
|
params.LoopInSource =
|
||||||
|
looprpc.LoopInSource_LOOP_IN_SOURCE_STATIC_ADDRESS
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown loopinsource value %q "+
|
||||||
|
"(use \"wallet\" or \"static-address\")",
|
||||||
|
cmd.String("loopinsource"))
|
||||||
|
}
|
||||||
|
|
||||||
|
flagSet = true
|
||||||
|
}
|
||||||
|
|
||||||
// If we are setting easy autoloop parameters, we need to ensure that
|
// If we are setting easy autoloop parameters, we need to ensure that
|
||||||
// the asset ID is set, and that we have a valid entry in our params
|
// the asset ID is set, and that we have a valid entry in our params
|
||||||
// map.
|
// map.
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ func loopIn(ctx context.Context, cmd *cli.Command) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Swap initiated\n")
|
fmt.Printf("Swap initiated\n")
|
||||||
fmt.Printf("ID: %v\n", resp.Id)
|
fmt.Printf("ID: %x\n", resp.IdBytes)
|
||||||
|
|
||||||
if resp.HtlcAddressP2Tr != "" {
|
if resp.HtlcAddressP2Tr != "" {
|
||||||
fmt.Printf("HTLC address (P2TR): %v\n", resp.HtlcAddressP2Tr)
|
fmt.Printf("HTLC address (P2TR): %v\n", resp.HtlcAddressP2Tr)
|
||||||
|
|
|
||||||
|
|
@ -248,9 +248,9 @@ func loopOut(ctx context.Context, cmd *cli.Command) error {
|
||||||
// Set our maximum swap wait time. If a fast swap is requested we set
|
// Set our maximum swap wait time. If a fast swap is requested we set
|
||||||
// it to now, otherwise to 30 minutes in the future.
|
// it to now, otherwise to 30 minutes in the future.
|
||||||
fast := cmd.Bool("fast")
|
fast := cmd.Bool("fast")
|
||||||
swapDeadline := time.Now()
|
swapDeadline := cliClock.Now()
|
||||||
if !fast {
|
if !fast {
|
||||||
swapDeadline = time.Now().Add(defaultSwapWaitTime)
|
swapDeadline = cliClock.Now().Add(defaultSwapWaitTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
sweepConfTarget := int32(cmd.Uint64("conf_target"))
|
sweepConfTarget := int32(cmd.Uint64("conf_target"))
|
||||||
|
|
|
||||||
327
cmd/loop/main.go
327
cmd/loop/main.go
|
|
@ -6,11 +6,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
|
@ -19,6 +20,7 @@ import (
|
||||||
"github.com/lightninglabs/loop/loopd"
|
"github.com/lightninglabs/loop/loopd"
|
||||||
"github.com/lightninglabs/loop/looprpc"
|
"github.com/lightninglabs/loop/looprpc"
|
||||||
"github.com/lightninglabs/loop/swap"
|
"github.com/lightninglabs/loop/swap"
|
||||||
|
"github.com/lightningnetwork/lnd/clock"
|
||||||
"github.com/lightningnetwork/lnd/lncfg"
|
"github.com/lightningnetwork/lnd/lncfg"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/macaroons"
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
|
|
@ -52,10 +54,11 @@ var (
|
||||||
defaultInitiator = "loop-cli"
|
defaultInitiator = "loop-cli"
|
||||||
|
|
||||||
loopDirFlag = &cli.StringFlag{
|
loopDirFlag = &cli.StringFlag{
|
||||||
Name: "loopdir",
|
Name: "loopdir",
|
||||||
Value: loopd.LoopDirBase,
|
Value: loopd.LoopDirBase,
|
||||||
Usage: "path to loop's base directory",
|
DefaultText: defaultPathText(loopd.LoopDirBase, os.UserHomeDir),
|
||||||
Sources: cli.EnvVars(envVarLoopDir),
|
Usage: "path to loop's base directory",
|
||||||
|
Sources: cli.EnvVars(envVarLoopDir),
|
||||||
}
|
}
|
||||||
networkFlag = &cli.StringFlag{
|
networkFlag = &cli.StringFlag{
|
||||||
Name: "network",
|
Name: "network",
|
||||||
|
|
@ -66,15 +69,21 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsCertFlag = &cli.StringFlag{
|
tlsCertFlag = &cli.StringFlag{
|
||||||
Name: "tlscertpath",
|
Name: "tlscertpath",
|
||||||
Usage: "path to loop's TLS certificate",
|
Usage: "path to loop's TLS certificate",
|
||||||
Value: loopd.DefaultTLSCertPath,
|
Value: loopd.DefaultTLSCertPath,
|
||||||
|
DefaultText: defaultPathText(
|
||||||
|
loopd.DefaultTLSCertPath, os.UserHomeDir,
|
||||||
|
),
|
||||||
Sources: cli.EnvVars(envVarTLSCertPath),
|
Sources: cli.EnvVars(envVarTLSCertPath),
|
||||||
}
|
}
|
||||||
macaroonPathFlag = &cli.StringFlag{
|
macaroonPathFlag = &cli.StringFlag{
|
||||||
Name: "macaroonpath",
|
Name: "macaroonpath",
|
||||||
Usage: "path to macaroon file",
|
Usage: "path to macaroon file",
|
||||||
Value: loopd.DefaultMacaroonPath,
|
Value: loopd.DefaultMacaroonPath,
|
||||||
|
DefaultText: defaultPathText(
|
||||||
|
loopd.DefaultMacaroonPath, os.UserHomeDir,
|
||||||
|
),
|
||||||
Sources: cli.EnvVars(envVarMacaroonPath),
|
Sources: cli.EnvVars(envVarMacaroonPath),
|
||||||
}
|
}
|
||||||
verboseFlag = &cli.BoolFlag{
|
verboseFlag = &cli.BoolFlag{
|
||||||
|
|
@ -92,8 +101,53 @@ var (
|
||||||
instantOutCommand, listInstantOutsCommand, stopCommand,
|
instantOutCommand, listInstantOutsCommand, stopCommand,
|
||||||
printManCommand, printMarkdownCommand,
|
printManCommand, printMarkdownCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cliClock provides the time source used by CLI commands.
|
||||||
|
cliClock clock.Clock = clock.NewDefaultClock()
|
||||||
|
|
||||||
|
// sessionRec is the active recorder when session capture is enabled.
|
||||||
|
sessionRec *sessionRecorder
|
||||||
|
|
||||||
|
// forceDeterministicJSON is enabled by tests to obtain stable JSON
|
||||||
|
// output.
|
||||||
|
forceDeterministicJSON bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// installSessionSignalHandler records signals and cancels the root context.
|
||||||
|
func installSessionSignalHandler(cancel context.CancelFunc) func() {
|
||||||
|
if sessionRec == nil {
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
interrupted := false
|
||||||
|
for sig := range sigCh {
|
||||||
|
sessionRec.LogSignal(sig)
|
||||||
|
if !interrupted {
|
||||||
|
interrupted = true
|
||||||
|
cancel()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// A second signal requests immediate shutdown. Finalize the
|
||||||
|
// session first because os.Exit skips deferred cleanup.
|
||||||
|
_ = sessionRec.Finalize(fmt.Errorf("signal: %s", sig))
|
||||||
|
os.Exit(130)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
signal.Stop(sigCh)
|
||||||
|
close(sigCh)
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
||||||
// satAmtFmt formats a satoshi value into a one line string, intended to
|
// satAmtFmt formats a satoshi value into a one line string, intended to
|
||||||
|
|
@ -132,6 +186,37 @@ const (
|
||||||
envVarMacaroonPath = "LOOPCLI_MACAROONPATH"
|
envVarMacaroonPath = "LOOPCLI_MACAROONPATH"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// defaultPathText returns a help-friendly path string that replaces the user's
|
||||||
|
// home directory with "~". The homeDir function is injected so callers can
|
||||||
|
// control environment-dependent behavior in tests.
|
||||||
|
func defaultPathText(value string, homeDir func() (string, error)) string {
|
||||||
|
if value == "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
if homeDir == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
home, err := homeDir()
|
||||||
|
if err != nil || home == "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanHome := filepath.Clean(home)
|
||||||
|
cleanValue := filepath.Clean(value)
|
||||||
|
if cleanValue == cleanHome {
|
||||||
|
return "~"
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := cleanHome + string(filepath.Separator)
|
||||||
|
if suffix, ok := strings.CutPrefix(cleanValue, prefix); ok {
|
||||||
|
return "~" + string(filepath.Separator) + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
func printJSON(resp any) {
|
func printJSON(resp any) {
|
||||||
b, err := json.Marshal(resp)
|
b, err := json.Marshal(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -144,7 +229,8 @@ func printJSON(resp any) {
|
||||||
fatal(err)
|
fatal(err)
|
||||||
}
|
}
|
||||||
out.WriteString("\n")
|
out.WriteString("\n")
|
||||||
_, _ = out.WriteTo(os.Stdout)
|
printBytes := maybeNormalizeJSON(out.Bytes())
|
||||||
|
_, _ = os.Stdout.Write(printBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printRespJSON(resp proto.Message) {
|
func printRespJSON(resp proto.Message) {
|
||||||
|
|
@ -154,16 +240,76 @@ func printRespJSON(resp proto.Message) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(jsonBytes))
|
fmt.Println(string(maybeNormalizeJSON(jsonBytes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func fatal(err error) {
|
func fatal(err error) {
|
||||||
fmt.Fprintf(os.Stderr, "[loop] %v\n", err)
|
fmt.Fprintf(os.Stderr, "[loop] %v\n", err)
|
||||||
|
if sessionRec != nil {
|
||||||
|
if finalizeErr := sessionRec.Finalize(err); finalizeErr != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[loop] unable to finalize "+
|
||||||
|
"session: %v\n", finalizeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rootCmd := &cli.Command{
|
var err error
|
||||||
|
sessionRec, err = newSessionRecorder(os.Args)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept clock and stdio if needed.
|
||||||
|
if sessionRec != nil {
|
||||||
|
restoreClock := hookClock(
|
||||||
|
clock.NewTestClock(sessionRec.ClockStart()),
|
||||||
|
)
|
||||||
|
defer restoreClock()
|
||||||
|
|
||||||
|
if err := sessionRec.Start(nil, nil, nil); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intercept clock calls if needed.
|
||||||
|
var restoreTransport func()
|
||||||
|
if sessionRec != nil {
|
||||||
|
restoreTransport = hookGrpc(sessionRec)
|
||||||
|
defer restoreTransport()
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd := newRootCommand()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if sessionRec != nil {
|
||||||
|
ctx = sessionRec.InjectContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessionRec != nil {
|
||||||
|
signalStop := installSessionSignalHandler(cancel)
|
||||||
|
defer signalStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rootCmd.Run(ctx, os.Args); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessionRec != nil {
|
||||||
|
if err := sessionRec.Finalize(nil); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "[loop] unable to finalize "+
|
||||||
|
"session: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRootCommand constructs the CLI root command for loop.
|
||||||
|
func newRootCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
Name: "loop",
|
Name: "loop",
|
||||||
Usage: "control plane for your loopd",
|
Usage: "control plane for your loopd",
|
||||||
Version: loop.RichVersion(),
|
Version: loop.RichVersion(),
|
||||||
|
|
@ -184,17 +330,11 @@ func main() {
|
||||||
return cli.ShowRootCommandHelp(cmd)
|
return cli.ShowRootCommandHelp(cmd)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rootCmd.Run(context.Background(), os.Args); err != nil {
|
|
||||||
fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getClient establishes a SwapClient RPC connection and returns the client and
|
// getClient establishes a SwapClient RPC connection and returns the client and
|
||||||
// a cleanup handler.
|
// a cleanup handler.
|
||||||
func getClient(cmd *cli.Command) (looprpc.SwapClientClient,
|
func getClient(cmd *cli.Command) (looprpc.SwapClientClient, func(), error) {
|
||||||
func(), error) {
|
|
||||||
|
|
||||||
client, _, cleanup, err := getClientWithConn(cmd)
|
client, _, cleanup, err := getClientWithConn(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
@ -206,23 +346,54 @@ func getClient(cmd *cli.Command) (looprpc.SwapClientClient,
|
||||||
// getClientWithConn returns both the SwapClient RPC client and the underlying
|
// getClientWithConn returns both the SwapClient RPC client and the underlying
|
||||||
// gRPC connection so callers can perform connection-aware actions.
|
// gRPC connection so callers can perform connection-aware actions.
|
||||||
func getClientWithConn(cmd *cli.Command) (looprpc.SwapClientClient,
|
func getClientWithConn(cmd *cli.Command) (looprpc.SwapClientClient,
|
||||||
*grpc.ClientConn, func(), error) {
|
daemonConn, func(), error) {
|
||||||
|
|
||||||
rpcServer := cmd.String("rpcserver")
|
conn, cleanup, err := sessionTransport.Dial(cmd)
|
||||||
tlsCertPath, macaroonPath, err := extractPathArgs(cmd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
cleanup := func() { conn.Close() }
|
|
||||||
|
|
||||||
loopClient := looprpc.NewSwapClientClient(conn)
|
loopClient := looprpc.NewSwapClientClient(conn)
|
||||||
return loopClient, conn, cleanup, nil
|
return loopClient, conn, cleanup, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hookClock overrides cliClock until the returned callback is called.
|
||||||
|
func hookClock(c clock.Clock) func() {
|
||||||
|
prev := cliClock
|
||||||
|
cliClock = c
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
cliClock = prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybeNormalizeJSON rewrites JSON output to avoid the build-dependent spacing
|
||||||
|
// introduced by google.golang.org/protobuf/internal/encoding/json (see
|
||||||
|
// WriteName in protobuf-go-hex-display/internal/encoding/json/encode.go, which
|
||||||
|
// uses internal/detrand.Bool). When recording or replaying sessions we ensure
|
||||||
|
// stable output by re-encoding with the standard library.
|
||||||
|
func maybeNormalizeJSON(raw []byte) []byte {
|
||||||
|
if sessionRec == nil && !forceDeterministicJSON {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsed any
|
||||||
|
if err := json.Unmarshal(raw, &parsed); err != nil {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized, err := json.MarshalIndent(parsed, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(raw) > 0 && raw[len(raw)-1] == '\n' {
|
||||||
|
normalized = append(normalized, '\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount {
|
func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount {
|
||||||
return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate)
|
return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate)
|
||||||
}
|
}
|
||||||
|
|
@ -329,12 +500,6 @@ func displayInDetails(req *looprpc.QuoteRequest,
|
||||||
"wallet.\n\n")
|
"wallet.\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.DepositOutpoints != nil {
|
|
||||||
fmt.Printf("On-chain fees for static address loop-ins are not " +
|
|
||||||
"included.\nThey were already paid when the deposits " +
|
|
||||||
"were created.\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
printQuoteInResp(req, resp, verbose)
|
printQuoteInResp(req, resp, verbose)
|
||||||
|
|
||||||
fmt.Printf("\nCONTINUE SWAP? (y/n): ")
|
fmt.Printf("\nCONTINUE SWAP? (y/n): ")
|
||||||
|
|
@ -395,10 +560,7 @@ func parseAmt(text string) (btcutil.Amount, error) {
|
||||||
|
|
||||||
func logSwap(swap *looprpc.SwapStatus) {
|
func logSwap(swap *looprpc.SwapStatus) {
|
||||||
// If our swap failed, we add our failure reason to the state.
|
// If our swap failed, we add our failure reason to the state.
|
||||||
swapState := fmt.Sprintf("%v", swap.State)
|
swapState := monitorSwapState(swap)
|
||||||
if swap.State == looprpc.SwapState_FAILED {
|
|
||||||
swapState = fmt.Sprintf("%v (%v)", swapState, swap.FailureReason)
|
|
||||||
}
|
|
||||||
|
|
||||||
if swap.Type == looprpc.SwapType_LOOP_OUT {
|
if swap.Type == looprpc.SwapType_LOOP_OUT {
|
||||||
fmt.Printf("%v %v %v %v - %v",
|
fmt.Printf("%v %v %v %v - %v",
|
||||||
|
|
@ -420,10 +582,32 @@ func logSwap(swap *looprpc.SwapStatus) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if swap.State != looprpc.SwapState_INITIATED &&
|
showCost := shouldShowSwapCost(swap.GetState())
|
||||||
swap.State != looprpc.SwapState_HTLC_PUBLISHED &&
|
if swap.Type == looprpc.SwapType_STATIC_LOOP_IN {
|
||||||
swap.State != looprpc.SwapState_PREIMAGE_REVEALED {
|
staticState := swap.GetStaticLoopInState()
|
||||||
|
// Static loop-ins key cost visibility off the dedicated FSM state, not
|
||||||
|
// the generic SwapState lifecycle used by traditional swaps.
|
||||||
|
switch staticState {
|
||||||
|
case looprpc.StaticAddressLoopInSwapState_INIT_HTLC,
|
||||||
|
looprpc.StaticAddressLoopInSwapState_SIGN_HTLC_TX,
|
||||||
|
looprpc.StaticAddressLoopInSwapState_MONITOR_INVOICE_HTLC_TX,
|
||||||
|
looprpc.StaticAddressLoopInSwapState_SWEEP_STATIC_ADDRESS_HTLC_TIMEOUT,
|
||||||
|
looprpc.StaticAddressLoopInSwapState_MONITOR_HTLC_TIMEOUT_SWEEP,
|
||||||
|
looprpc.StaticAddressLoopInSwapState_UNLOCK_DEPOSITS:
|
||||||
|
|
||||||
|
showCost = false
|
||||||
|
|
||||||
|
case looprpc.StaticAddressLoopInSwapState_PAYMENT_RECEIVED,
|
||||||
|
looprpc.StaticAddressLoopInSwapState_HTLC_STATIC_ADDRESS_TIMEOUT_SWEPT,
|
||||||
|
looprpc.StaticAddressLoopInSwapState_SUCCEEDED,
|
||||||
|
looprpc.StaticAddressLoopInSwapState_SUCCEEDED_TRANSITIONING_FAILED,
|
||||||
|
looprpc.StaticAddressLoopInSwapState_FAILED_STATIC_ADDRESS_SWAP:
|
||||||
|
|
||||||
|
showCost = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if showCost {
|
||||||
fmt.Printf(" (cost: server %v, onchain %v, offchain %v)",
|
fmt.Printf(" (cost: server %v, onchain %v, offchain %v)",
|
||||||
swap.CostServer, swap.CostOnchain, swap.CostOffchain,
|
swap.CostServer, swap.CostOnchain, swap.CostOffchain,
|
||||||
)
|
)
|
||||||
|
|
@ -432,13 +616,46 @@ func logSwap(swap *looprpc.SwapStatus) {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
|
// monitorSwapState returns the static loop-in FSM label for static swaps and
|
||||||
error) {
|
// the shared swap-state label for all others.
|
||||||
|
func monitorSwapState(swap *looprpc.SwapStatus) string {
|
||||||
|
if swap.Type == looprpc.SwapType_STATIC_LOOP_IN {
|
||||||
|
return swap.GetStaticLoopInState().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return genericMonitorSwapState(swap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldShowSwapCost reports whether a swap's generic state is terminal enough
|
||||||
|
// to include the persisted cost summary in monitor output.
|
||||||
|
func shouldShowSwapCost(loopState looprpc.SwapState) bool {
|
||||||
|
return loopState != looprpc.SwapState_INITIATED &&
|
||||||
|
loopState != looprpc.SwapState_HTLC_PUBLISHED &&
|
||||||
|
loopState != looprpc.SwapState_PREIMAGE_REVEALED
|
||||||
|
}
|
||||||
|
|
||||||
|
// genericMonitorSwapState formats the shared swap-state label and failure
|
||||||
|
// reason used by non-static swaps.
|
||||||
|
func genericMonitorSwapState(swap *looprpc.SwapStatus) string {
|
||||||
|
loopState := swap.GetState()
|
||||||
|
swapState := fmt.Sprintf("%v", loopState)
|
||||||
|
if loopState == looprpc.SwapState_FAILED {
|
||||||
|
swapState = fmt.Sprintf(
|
||||||
|
"%v (%v)", swapState, swap.FailureReason,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return swapState
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClientConn dials the loopd gRPC server with TLS and macaroon auth.
|
||||||
|
func getClientConn(address, tlsCertPath, macaroonPath string) (daemonConn,
|
||||||
|
func(), error) {
|
||||||
|
|
||||||
// We always need to send a macaroon.
|
// We always need to send a macaroon.
|
||||||
macOption, err := readMacaroon(macaroonPath)
|
macOption, err := readMacaroon(macaroonPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []grpc.DialOption{
|
opts := []grpc.DialOption{
|
||||||
|
|
@ -446,28 +663,40 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
|
||||||
macOption,
|
macOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Install gRPC interceptors for session recording if needed.
|
||||||
|
if unary := sessionTransport.UnaryInterceptor(); unary != nil {
|
||||||
|
opts = append(opts, grpc.WithChainUnaryInterceptor(unary))
|
||||||
|
}
|
||||||
|
if stream := sessionTransport.StreamInterceptor(); stream != nil {
|
||||||
|
opts = append(opts, grpc.WithChainStreamInterceptor(stream))
|
||||||
|
}
|
||||||
|
|
||||||
// Since TLS cannot be disabled, we'll always have a cert file to read.
|
// Since TLS cannot be disabled, we'll always have a cert file to read.
|
||||||
creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "")
|
creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = append(opts, grpc.WithTransportCredentials(creds))
|
opts = append(opts, grpc.WithTransportCredentials(creds))
|
||||||
|
|
||||||
conn, err := grpc.NewClient(address, opts...)
|
conn, err := grpc.NewClient(address, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create RPC client: %v",
|
return nil, nil, fmt.Errorf("unable to create RPC client: %v",
|
||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return conn, nil
|
cleanup := func() {
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, cleanup, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readMacaroon tries to read the macaroon file at the specified path and create
|
// readMacaroon tries to read the macaroon file at the specified path and create
|
||||||
// gRPC dial options from it.
|
// gRPC dial options from it.
|
||||||
func readMacaroon(macPath string) (grpc.DialOption, error) {
|
func readMacaroon(macPath string) (grpc.DialOption, error) {
|
||||||
// Load the specified macaroon file.
|
// Load the specified macaroon file.
|
||||||
macBytes, err := ioutil.ReadFile(macPath)
|
macBytes, err := os.ReadFile(macPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
|
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
275
cmd/loop/monitor_test.go
Normal file
275
cmd/loop/monitor_test.go
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightninglabs/loop/looprpc"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMonitorSwapStateKeepsRegularLoopInFailureReason preserves the generic
|
||||||
|
// failure suffix for regular loop-in swaps so terminal errors stay visible.
|
||||||
|
func TestMonitorSwapStateKeepsRegularLoopInFailureReason(t *testing.T) {
|
||||||
|
swap := &looprpc.SwapStatus{
|
||||||
|
Type: looprpc.SwapType_LOOP_IN,
|
||||||
|
State: looprpc.SwapState_FAILED,
|
||||||
|
FailureReason: looprpc.
|
||||||
|
FailureReason_FAILURE_REASON_TIMEOUT,
|
||||||
|
}
|
||||||
|
|
||||||
|
got := monitorSwapState(swap)
|
||||||
|
require.Equal(t, "FAILED (FAILURE_REASON_TIMEOUT)", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMonitorSwapStateLabelsStaticLoopInStages locks the precise static loop-in
|
||||||
|
// stage names so monitor output stays stable as the FSM evolves.
|
||||||
|
func TestMonitorSwapStateLabelsStaticLoopInStages(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
staticState looprpc.StaticAddressLoopInSwapState
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "init htlc",
|
||||||
|
staticState: looprpc.StaticAddressLoopInSwapState_INIT_HTLC,
|
||||||
|
want: "INIT_HTLC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sign htlc",
|
||||||
|
staticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_SIGN_HTLC_TX,
|
||||||
|
want: "SIGN_HTLC_TX",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "monitor invoice and htlc",
|
||||||
|
staticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_MONITOR_INVOICE_HTLC_TX,
|
||||||
|
want: "MONITOR_INVOICE_HTLC_TX",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unlock deposits",
|
||||||
|
staticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_UNLOCK_DEPOSITS,
|
||||||
|
want: "UNLOCK_DEPOSITS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "payment received",
|
||||||
|
staticState: looprpc.StaticAddressLoopInSwapState_PAYMENT_RECEIVED,
|
||||||
|
want: "PAYMENT_RECEIVED",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timeout sweep",
|
||||||
|
staticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_SWEEP_STATIC_ADDRESS_HTLC_TIMEOUT,
|
||||||
|
want: "SWEEP_STATIC_ADDRESS_HTLC_TIMEOUT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "monitor timeout sweep",
|
||||||
|
staticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_MONITOR_HTLC_TIMEOUT_SWEEP,
|
||||||
|
want: "MONITOR_HTLC_TIMEOUT_SWEEP",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timeout swept",
|
||||||
|
staticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_HTLC_STATIC_ADDRESS_TIMEOUT_SWEPT,
|
||||||
|
want: "HTLC_STATIC_ADDRESS_TIMEOUT_SWEPT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
staticState: looprpc.StaticAddressLoopInSwapState_SUCCEEDED,
|
||||||
|
want: "SUCCEEDED",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "succeeded transitioning failed",
|
||||||
|
staticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_SUCCEEDED_TRANSITIONING_FAILED,
|
||||||
|
want: "SUCCEEDED_TRANSITIONING_FAILED",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "failed",
|
||||||
|
staticState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_FAILED_STATIC_ADDRESS_SWAP,
|
||||||
|
want: "FAILED_STATIC_ADDRESS_SWAP",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
swap := &looprpc.SwapStatus{
|
||||||
|
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||||
|
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||||
|
StaticLoopInState: test.staticState,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := monitorSwapState(swap)
|
||||||
|
require.Equal(t, test.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
initSwap := &looprpc.SwapStatus{
|
||||||
|
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||||
|
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||||
|
StaticLoopInState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_INIT_HTLC,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
signSwap := &looprpc.SwapStatus{
|
||||||
|
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||||
|
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||||
|
StaticLoopInState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_SIGN_HTLC_TX,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NotEqual(t, monitorSwapState(initSwap), monitorSwapState(signSwap))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMonitorSwapStateLabelsUnknownStaticLoopIn ensures the absent static
|
||||||
|
// oneof maps to the UNKNOWN static loop-in label.
|
||||||
|
func TestMonitorSwapStateLabelsUnknownStaticLoopIn(t *testing.T) {
|
||||||
|
swap := &looprpc.SwapStatus{
|
||||||
|
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||||
|
}
|
||||||
|
|
||||||
|
got := monitorSwapState(swap)
|
||||||
|
require.Equal(t, "UNKNOWN_STATIC_ADDRESS_SWAP_STATE", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLogSwapHidesStaticLoopInCostForInFlightState proves in-flight static FSM
|
||||||
|
// state suppresses cost even with generic SUCCESS set deliberately.
|
||||||
|
func TestLogSwapHidesStaticLoopInCostForInFlightState(t *testing.T) {
|
||||||
|
swap := &looprpc.SwapStatus{
|
||||||
|
LastUpdateTime: time.Unix(1, 0).UnixNano(),
|
||||||
|
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||||
|
State: looprpc.SwapState_SUCCESS,
|
||||||
|
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||||
|
StaticLoopInState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_INIT_HTLC,
|
||||||
|
},
|
||||||
|
Amt: 50_000,
|
||||||
|
CostServer: 11,
|
||||||
|
CostOnchain: 22,
|
||||||
|
CostOffchain: 33,
|
||||||
|
HtlcAddressP2Wsh: "bc1qstaticinflighttestaddress",
|
||||||
|
}
|
||||||
|
|
||||||
|
output := captureStdout(t, func() {
|
||||||
|
logSwap(swap)
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Contains(t, output, "STATIC_LOOP_IN INIT_HTLC 0.00050000 BTC")
|
||||||
|
require.NotContains(t, output, "(cost:")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLogSwapHidesCostForUnknownStaticLoopInState ensures unknown static
|
||||||
|
// states print the UNKNOWN label without a cost summary.
|
||||||
|
func TestLogSwapHidesCostForUnknownStaticLoopInState(t *testing.T) {
|
||||||
|
swap := &looprpc.SwapStatus{
|
||||||
|
LastUpdateTime: time.Unix(3, 0).UnixNano(),
|
||||||
|
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||||
|
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||||
|
StaticLoopInState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_UNKNOWN_STATIC_ADDRESS_SWAP_STATE,
|
||||||
|
},
|
||||||
|
Amt: 50_000,
|
||||||
|
CostServer: 11,
|
||||||
|
CostOnchain: 22,
|
||||||
|
CostOffchain: 33,
|
||||||
|
HtlcAddressP2Wsh: "bc1qunknownstaticterminaltestaddress",
|
||||||
|
}
|
||||||
|
|
||||||
|
output := captureStdout(t, func() {
|
||||||
|
logSwap(swap)
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Contains(
|
||||||
|
t, output, "STATIC_LOOP_IN UNKNOWN_STATIC_ADDRESS_SWAP_STATE 0.00050000 BTC",
|
||||||
|
)
|
||||||
|
require.NotContains(t, output, "(cost:")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLogSwapShowsStaticLoopInCostForTerminalState preserves terminal cost
|
||||||
|
// output, including the P2WSH address, once the static loop-in is done.
|
||||||
|
func TestLogSwapShowsStaticLoopInCostForTerminalState(t *testing.T) {
|
||||||
|
swap := &looprpc.SwapStatus{
|
||||||
|
LastUpdateTime: time.Unix(2, 0).UnixNano(),
|
||||||
|
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||||
|
State: looprpc.SwapState_INITIATED,
|
||||||
|
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||||
|
StaticLoopInState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_SUCCEEDED,
|
||||||
|
},
|
||||||
|
Amt: 50_000,
|
||||||
|
CostServer: 11,
|
||||||
|
CostOnchain: 22,
|
||||||
|
CostOffchain: 33,
|
||||||
|
HtlcAddressP2Wsh: "bc1qstaticterminaltestaddress",
|
||||||
|
}
|
||||||
|
|
||||||
|
output := captureStdout(t, func() {
|
||||||
|
logSwap(swap)
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Contains(
|
||||||
|
t, output,
|
||||||
|
"STATIC_LOOP_IN SUCCEEDED 0.00050000 BTC - P2WSH: bc1qstaticterminaltestaddress",
|
||||||
|
)
|
||||||
|
require.Contains(
|
||||||
|
t, output,
|
||||||
|
"(cost: server 11, onchain 22, offchain 33)",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLogSwapDisplaysStaticLoopInTypeAndStage ensures the monitor output names
|
||||||
|
// the static loop-in type and active FSM stage instead of collapsing them.
|
||||||
|
func TestLogSwapDisplaysStaticLoopInTypeAndStage(t *testing.T) {
|
||||||
|
swap := &looprpc.SwapStatus{
|
||||||
|
LastUpdateTime: time.Unix(1, 0).UnixNano(),
|
||||||
|
Type: looprpc.SwapType_STATIC_LOOP_IN,
|
||||||
|
StaticLoopInStateOptional: &looprpc.SwapStatus_StaticLoopInState{
|
||||||
|
StaticLoopInState: looprpc.
|
||||||
|
StaticAddressLoopInSwapState_SIGN_HTLC_TX,
|
||||||
|
},
|
||||||
|
Amt: 50_000,
|
||||||
|
HtlcAddressP2Wsh: "tb1q5cyxnuxmeuwuvkwfem96llyxf8duyshm56t8k8",
|
||||||
|
}
|
||||||
|
|
||||||
|
output := captureStdout(t, func() {
|
||||||
|
logSwap(swap)
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Contains(
|
||||||
|
t, output, "STATIC_LOOP_IN SIGN_HTLC_TX 0.00050000 BTC",
|
||||||
|
)
|
||||||
|
require.Contains(
|
||||||
|
t, output, "P2WSH: tb1q5cyxnuxmeuwuvkwfem96llyxf8duyshm56t8k8",
|
||||||
|
)
|
||||||
|
require.NotContains(t, output, "STATIC_LOOP_IN INIT_HTLC")
|
||||||
|
}
|
||||||
|
|
||||||
|
func captureStdout(t *testing.T, fn func()) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
originalStdout := os.Stdout
|
||||||
|
reader, writer, err := os.Pipe()
|
||||||
|
require.NoError(t, err)
|
||||||
|
os.Stdout = writer
|
||||||
|
|
||||||
|
fn()
|
||||||
|
|
||||||
|
require.NoError(t, writer.Close())
|
||||||
|
os.Stdout = originalStdout
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = io.Copy(&buf, reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, reader.Close())
|
||||||
|
|
||||||
|
return strings.TrimSpace(buf.String())
|
||||||
|
}
|
||||||
|
|
@ -7,24 +7,22 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/lightninglabs/loop/looprpc"
|
"github.com/lightninglabs/loop/looprpc"
|
||||||
lndcommands "github.com/lightningnetwork/lnd/cmd/commands"
|
"github.com/lightningnetwork/lnd"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultUtxoMinConf = 1
|
defaultUtxoMinConf = 1
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
channelTypeTweakless = "tweakless"
|
channelTypeTweakless = "tweakless"
|
||||||
channelTypeAnchors = "anchors"
|
channelTypeAnchors = "anchors"
|
||||||
channelTypeSimpleTaproot = "taproot"
|
channelTypeSimpleTaproot = "simple-taproot"
|
||||||
|
channelTypeTaproot = "taproot"
|
||||||
)
|
)
|
||||||
|
|
||||||
var openChannelCommand = &cli.Command{
|
var openChannelCommand = &cli.Command{
|
||||||
Name: "openchannel",
|
Name: "openchannel",
|
||||||
Usage: "Open a channel to a an existing peer.",
|
Usage: "Open a channel to an existing peer.",
|
||||||
Description: `
|
Description: `
|
||||||
Attempt to open a new channel to an existing peer with the key
|
Attempt to open a new channel to an existing peer with the key
|
||||||
node-key.
|
node-key.
|
||||||
|
|
@ -137,9 +135,9 @@ var openChannelCommand = &cli.Command{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "channel_type",
|
Name: "channel_type",
|
||||||
Usage: fmt.Sprintf("(optional) the type of channel to "+
|
Usage: fmt.Sprintf("(optional) the type of channel to "+
|
||||||
"propose to the remote peer (%q, %q, %q)",
|
"propose to the remote peer (%q, %q, %q, %q)",
|
||||||
channelTypeTweakless, channelTypeAnchors,
|
channelTypeTweakless, channelTypeAnchors,
|
||||||
channelTypeSimpleTaproot),
|
channelTypeSimpleTaproot, channelTypeTaproot),
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "zero_conf",
|
Name: "zero_conf",
|
||||||
|
|
@ -160,11 +158,11 @@ var openChannelCommand = &cli.Command{
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "memo",
|
Name: "memo",
|
||||||
Usage: `(optional) a note-to-self containing some useful
|
Usage: "(optional) a note-to-self containing some useful " +
|
||||||
information about the channel. This is stored
|
"information about the channel. This is stored " +
|
||||||
locally only, and is purely for reference. It
|
"locally only, and is purely for reference. It " +
|
||||||
has no bearing on the channel's operation. Max
|
"has no bearing on the channel's operation. Max " +
|
||||||
allowed length is 500 characters`,
|
"allowed length is 500 characters",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "fundmax",
|
Name: "fundmax",
|
||||||
|
|
@ -271,7 +269,7 @@ func openChannel(ctx context.Context, cmd *cli.Command) error {
|
||||||
if cmd.IsSet("utxo") {
|
if cmd.IsSet("utxo") {
|
||||||
utxos := cmd.StringSlice("utxo")
|
utxos := cmd.StringSlice("utxo")
|
||||||
|
|
||||||
outpoints, err := lndcommands.UtxosToOutpoints(utxos)
|
outpoints, err := lnd.UtxosToOutpoints(utxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to decode utxos: %w", err)
|
return fmt.Errorf("unable to decode utxos: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -322,6 +320,7 @@ func openChannel(ctx context.Context, cmd *cli.Command) error {
|
||||||
switch channelType {
|
switch channelType {
|
||||||
case "":
|
case "":
|
||||||
break
|
break
|
||||||
|
|
||||||
case channelTypeTweakless:
|
case channelTypeTweakless:
|
||||||
req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
|
req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
|
||||||
|
|
||||||
|
|
@ -330,6 +329,10 @@ func openChannel(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
|
||||||
case channelTypeSimpleTaproot:
|
case channelTypeSimpleTaproot:
|
||||||
req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
|
||||||
|
|
||||||
|
case channelTypeTaproot:
|
||||||
|
req.CommitmentType = lnrpc.CommitmentType_TAPROOT
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported channel type %v", channelType)
|
return fmt.Errorf("unsupported channel type %v", channelType)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,9 +206,9 @@ func quoteOut(ctx context.Context, cmd *cli.Command) error {
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
fast := cmd.Bool("fast")
|
fast := cmd.Bool("fast")
|
||||||
swapDeadline := time.Now()
|
swapDeadline := cliClock.Now()
|
||||||
if !fast {
|
if !fast {
|
||||||
swapDeadline = time.Now().Add(defaultSwapWaitTime)
|
swapDeadline = cliClock.Now().Add(defaultSwapWaitTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
quoteReq := &looprpc.QuoteRequest{
|
quoteReq := &looprpc.QuoteRequest{
|
||||||
|
|
|
||||||
619
cmd/loop/session_fixture_update_test.go
Normal file
619
cmd/loop/session_fixture_update_test.go
Normal file
|
|
@ -0,0 +1,619 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// updateRecordedSessionsEnvVar enables replay bless mode for text-only fixture
|
||||||
|
// updates.
|
||||||
|
const updateRecordedSessionsEnvVar = "LOOP_UPDATE_RECORDED_SESSIONS"
|
||||||
|
|
||||||
|
// replayedSessionOutput captures the user-visible output produced by an
|
||||||
|
// offline session replay.
|
||||||
|
type replayedSessionOutput struct {
|
||||||
|
stdout string
|
||||||
|
stderr string
|
||||||
|
stdoutChunks []string
|
||||||
|
stderrChunks []string
|
||||||
|
runError *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateRecordedSessionsEnabled reports whether replay should bless text-only
|
||||||
|
// fixture updates.
|
||||||
|
func updateRecordedSessionsEnabled() (bool, error) {
|
||||||
|
raw, ok := os.LookupEnv(updateRecordedSessionsEnvVar)
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled, err := strconv.ParseBool(raw)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("invalid %s value %q",
|
||||||
|
updateRecordedSessionsEnvVar, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return enabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionFixturePath resolves a relative fixture path under
|
||||||
|
// cmd/loop/testdata/sessions to an absolute path.
|
||||||
|
func sessionFixturePath(rel string) (string, error) {
|
||||||
|
_, filename, _, ok := runtime.Caller(0)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("locate session fixture dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
loopDir := filepath.Dir(filename)
|
||||||
|
|
||||||
|
return filepath.Join(
|
||||||
|
loopDir, "testdata", "sessions", filepath.FromSlash(rel),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSessionFilePath reads and decodes a session fixture from disk.
|
||||||
|
func loadSessionFilePath(path string) (sessionFile, error) {
|
||||||
|
blob, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return sessionFile{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixture sessionFile
|
||||||
|
if err := json.Unmarshal(blob, &fixture); err != nil {
|
||||||
|
return sessionFile{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixture, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeSessionFilePath writes a session fixture using the recorder's JSON
|
||||||
|
// formatting.
|
||||||
|
func writeSessionFilePath(path string, fixture sessionFile) error {
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(file)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
|
||||||
|
return encoder.Encode(fixture)
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybeUpdateSessionFixture rewrites the text-only portions of a recorded
|
||||||
|
// session if the replay output changed.
|
||||||
|
func maybeUpdateSessionFixture(path string, fixture sessionFile,
|
||||||
|
output replayedSessionOutput) (bool, error) {
|
||||||
|
|
||||||
|
updated, changed, err := rewriteSessionFixture(fixture, output)
|
||||||
|
if err != nil || !changed {
|
||||||
|
return changed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeSessionFilePath(path, updated); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteSessionFixture rewrites stdout, stderr and run_error while leaving
|
||||||
|
// the rest of the recorded interaction unchanged.
|
||||||
|
func rewriteSessionFixture(fixture sessionFile,
|
||||||
|
output replayedSessionOutput) (sessionFile, bool, error) {
|
||||||
|
|
||||||
|
updated := fixture
|
||||||
|
changed := false
|
||||||
|
|
||||||
|
if !optionalTextEqual(updated.Metadata.RunError, output.runError) {
|
||||||
|
updated.Metadata.RunError = cloneOptionalString(output.runError)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
updated.Events, changed, err = rewriteExitEventRunError(
|
||||||
|
updated.Events, output.runError, changed,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return sessionFile{}, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated.Events, changed, err = rewriteTextEvents(
|
||||||
|
updated.Events, eventStdout, fixtureStdout(fixture),
|
||||||
|
output.stdout, output.stdoutChunks, changed,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return sessionFile{}, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated.Events, changed, err = rewriteTextEvents(
|
||||||
|
updated.Events, eventStderr, fixtureStderr(fixture),
|
||||||
|
output.stderr, output.stderrChunks, changed,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return sessionFile{}, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated, changed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteExitEventRunError updates the exit payload to match the replayed run
|
||||||
|
// error.
|
||||||
|
func rewriteExitEventRunError(events []sessionEvent, runError *string,
|
||||||
|
changed bool) ([]sessionEvent, bool, error) {
|
||||||
|
|
||||||
|
data, err := marshalSessionJSON(exitPayload{
|
||||||
|
RunError: cloneOptionalString(runError),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, changed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated := append([]sessionEvent(nil), events...)
|
||||||
|
|
||||||
|
for i, event := range slices.Backward(updated) {
|
||||||
|
if event.Kind != eventExit {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Equal(event.Data, data) {
|
||||||
|
return updated, changed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
updated[i].Data = data
|
||||||
|
|
||||||
|
return updated, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
updated = append(updated, sessionEvent{
|
||||||
|
Kind: eventExit,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
|
||||||
|
return updated, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteTextEvents updates one text stream when the normalized aggregate text
|
||||||
|
// changed.
|
||||||
|
func rewriteTextEvents(events []sessionEvent, kind, recorded, actual string,
|
||||||
|
chunks []string, changed bool) ([]sessionEvent, bool, error) {
|
||||||
|
|
||||||
|
normalizedEqual := normalizeTimestamps(recorded) ==
|
||||||
|
normalizeTimestamps(actual)
|
||||||
|
|
||||||
|
sourceChunks := chunks
|
||||||
|
sourceCombined := actual
|
||||||
|
if normalizedEqual {
|
||||||
|
sourceChunks = fixtureTextChunksByKind(events, kind)
|
||||||
|
sourceCombined = recorded
|
||||||
|
}
|
||||||
|
|
||||||
|
replacement, err := canonicalTextEvents(
|
||||||
|
events, kind, sourceChunks, sourceCombined,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, changed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if normalizedEqual && textEventsMatch(events, kind, replacement) {
|
||||||
|
return events, changed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := replaceTextEvents(events, kind, replacement)
|
||||||
|
if err != nil {
|
||||||
|
return nil, changed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// canonicalTextEvents builds the canonical replacement events for one text
|
||||||
|
// stream.
|
||||||
|
func canonicalTextEvents(events []sessionEvent, kind string, chunks []string,
|
||||||
|
combined string) ([]sessionEvent, error) {
|
||||||
|
|
||||||
|
indices := textEventIndices(events, kind)
|
||||||
|
replacement := replacementTextChunks(chunks, combined)
|
||||||
|
if len(indices) != len(replacement) {
|
||||||
|
replacement = replacementTextChunks(nil, combined)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildReplacementTextEvents(events, indices, kind, replacement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceTextEvents rewrites the recorded events for a text stream using the
|
||||||
|
// provided canonical replacement events.
|
||||||
|
func replaceTextEvents(events []sessionEvent, kind string,
|
||||||
|
replacements []sessionEvent) ([]sessionEvent, error) {
|
||||||
|
|
||||||
|
indices := textEventIndices(events, kind)
|
||||||
|
if len(indices) == 0 && len(replacements) == 0 {
|
||||||
|
return append([]sessionEvent(nil), events...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if replacements == nil {
|
||||||
|
replacements = []sessionEvent{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return replaceTextEventsWithCanonical(
|
||||||
|
events, kind, indices, replacements,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceTextEventsWithCanonical splices canonical replacement events into the
|
||||||
|
// event stream.
|
||||||
|
func replaceTextEventsWithCanonical(events []sessionEvent, kind string,
|
||||||
|
indices []int, replacements []sessionEvent) ([]sessionEvent, error) {
|
||||||
|
|
||||||
|
if len(indices) == len(replacements) {
|
||||||
|
updated := append([]sessionEvent(nil), events...)
|
||||||
|
for i, idx := range indices {
|
||||||
|
updated[idx].Data = replacements[i].Data
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(indices) > 0 {
|
||||||
|
firstIdx := indices[0]
|
||||||
|
updated := make([]sessionEvent, 0,
|
||||||
|
len(events)-len(indices)+len(replacements))
|
||||||
|
|
||||||
|
inserted := false
|
||||||
|
for i, event := range events {
|
||||||
|
if event.Kind == kind {
|
||||||
|
if !inserted && i == firstIdx {
|
||||||
|
updated = append(updated, replacements...)
|
||||||
|
inserted = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
updated = append(updated, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(replacements) == 0 {
|
||||||
|
return append([]sessionEvent(nil), events...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
insertAt := len(events)
|
||||||
|
for i, event := range events {
|
||||||
|
if event.Kind == eventExit {
|
||||||
|
insertAt = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updated := make([]sessionEvent, 0, len(events)+len(replacements))
|
||||||
|
updated = append(updated, events[:insertAt]...)
|
||||||
|
updated = append(updated, replacements...)
|
||||||
|
updated = append(updated, events[insertAt:]...)
|
||||||
|
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// textEventsMatch reports whether the existing events already use the canonical
|
||||||
|
// representation for a text stream.
|
||||||
|
func textEventsMatch(events []sessionEvent, kind string,
|
||||||
|
replacements []sessionEvent) bool {
|
||||||
|
|
||||||
|
indices := textEventIndices(events, kind)
|
||||||
|
if len(indices) != len(replacements) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, idx := range indices {
|
||||||
|
if events[idx].TimeMS != replacements[i].TimeMS {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(events[idx].Data, replacements[i].Data) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildReplacementTextEvents creates replacement text events while reusing the
|
||||||
|
// closest recorded timestamps when possible.
|
||||||
|
func buildReplacementTextEvents(events []sessionEvent, indices []int,
|
||||||
|
kind string, chunks []string) ([]sessionEvent, error) {
|
||||||
|
|
||||||
|
if len(chunks) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
replacements := make([]sessionEvent, 0, len(chunks))
|
||||||
|
for i, chunk := range chunks {
|
||||||
|
data, err := marshalSessionJSON(newTextPayload(chunk))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
replacements = append(replacements, sessionEvent{
|
||||||
|
TimeMS: replacementEventTime(events, indices, i),
|
||||||
|
Kind: kind,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return replacements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// replacementEventTime picks a stable timestamp for a replacement text event.
|
||||||
|
func replacementEventTime(events []sessionEvent, indices []int, idx int) int64 {
|
||||||
|
if len(indices) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if idx < len(indices) {
|
||||||
|
return events[indices[idx]].TimeMS
|
||||||
|
}
|
||||||
|
|
||||||
|
return events[indices[len(indices)-1]].TimeMS
|
||||||
|
}
|
||||||
|
|
||||||
|
// textEventIndices returns the indexes of text events with the given kind.
|
||||||
|
func textEventIndices(events []sessionEvent, kind string) []int {
|
||||||
|
var indices []int
|
||||||
|
for i, event := range events {
|
||||||
|
if event.Kind == kind {
|
||||||
|
indices = append(indices, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return indices
|
||||||
|
}
|
||||||
|
|
||||||
|
// replacementTextChunks returns the recorded write chunks to persist. If the
|
||||||
|
// hook callbacks did not observe any chunks, fall back to the aggregate text so
|
||||||
|
// the fixture still captures the visible output.
|
||||||
|
func replacementTextChunks(chunks []string, combined string) []string {
|
||||||
|
if len(chunks) > 0 {
|
||||||
|
return append([]string(nil), chunks...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if combined == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{combined}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixtureStdout returns the concatenated stdout text stored in a fixture.
|
||||||
|
func fixtureStdout(fixture sessionFile) string {
|
||||||
|
return fixtureTextByKind(fixture.Events, eventStdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixtureStderr returns the concatenated stderr text stored in a fixture.
|
||||||
|
func fixtureStderr(fixture sessionFile) string {
|
||||||
|
return fixtureTextByKind(fixture.Events, eventStderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixtureTextByKind concatenates all text payloads for a given event kind.
|
||||||
|
func fixtureTextByKind(events []sessionEvent, kind string) string {
|
||||||
|
var out bytes.Buffer
|
||||||
|
for _, text := range fixtureTextChunksByKind(events, kind) {
|
||||||
|
out.WriteString(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixtureTextChunksByKind returns the decoded text for each event of a given
|
||||||
|
// kind.
|
||||||
|
func fixtureTextChunksByKind(events []sessionEvent, kind string) []string {
|
||||||
|
var chunks []string
|
||||||
|
for _, event := range events {
|
||||||
|
if event.Kind != kind {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload textPayload
|
||||||
|
if err := json.Unmarshal(event.Data, &payload); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks = append(chunks, payload.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionalTextEqual compares optional text values using the same timestamp
|
||||||
|
// normalization rules as replay assertions.
|
||||||
|
func optionalTextEqual(a, b *string) bool {
|
||||||
|
switch {
|
||||||
|
case a == nil && b == nil:
|
||||||
|
return true
|
||||||
|
|
||||||
|
case a == nil || b == nil:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeTimestamps(*a) == normalizeTimestamps(*b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cloneOptionalString copies an optional string.
|
||||||
|
func cloneOptionalString(value *string) *string {
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cloned := *value
|
||||||
|
|
||||||
|
return &cloned
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUpdateRecordedSessionsEnabled verifies update-mode env parsing.
|
||||||
|
func TestUpdateRecordedSessionsEnabled(t *testing.T) {
|
||||||
|
if current, ok := os.LookupEnv(updateRecordedSessionsEnvVar); ok {
|
||||||
|
t.Setenv(updateRecordedSessionsEnvVar, current)
|
||||||
|
} else {
|
||||||
|
t.Setenv(updateRecordedSessionsEnvVar, "false")
|
||||||
|
}
|
||||||
|
_ = os.Unsetenv(updateRecordedSessionsEnvVar)
|
||||||
|
|
||||||
|
enabled, err := updateRecordedSessionsEnabled()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, enabled)
|
||||||
|
|
||||||
|
t.Setenv(updateRecordedSessionsEnvVar, "true")
|
||||||
|
enabled, err = updateRecordedSessionsEnabled()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, enabled)
|
||||||
|
|
||||||
|
t.Setenv(updateRecordedSessionsEnvVar, "invalid")
|
||||||
|
enabled, err = updateRecordedSessionsEnabled()
|
||||||
|
require.False(t, enabled)
|
||||||
|
require.EqualError(t, err,
|
||||||
|
"invalid LOOP_UPDATE_RECORDED_SESSIONS value \"invalid\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRewriteSessionFixtureUpdatesMatchingChunks verifies that bless mode
|
||||||
|
// rewrites text and run errors in place when the write counts are unchanged.
|
||||||
|
func TestRewriteSessionFixtureUpdatesMatchingChunks(t *testing.T) {
|
||||||
|
oldErr := "old error"
|
||||||
|
newErr := "new error"
|
||||||
|
|
||||||
|
fixture := sessionFile{
|
||||||
|
Metadata: sessionMetadata{
|
||||||
|
RunError: &oldErr,
|
||||||
|
},
|
||||||
|
Events: []sessionEvent{
|
||||||
|
textEvent(t, 5, eventStdout, "old stdout\n"),
|
||||||
|
textEvent(t, 6, eventStderr, "old stderr\n"),
|
||||||
|
exitEvent(t, 7, &oldErr),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, changed, err := rewriteSessionFixture(fixture,
|
||||||
|
replayedSessionOutput{
|
||||||
|
stdout: "new stdout\n",
|
||||||
|
stderr: "new stderr\n",
|
||||||
|
stdoutChunks: []string{"new stdout\n"},
|
||||||
|
stderrChunks: []string{"new stderr\n"},
|
||||||
|
runError: &newErr,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, changed)
|
||||||
|
require.Equal(t, &newErr, updated.Metadata.RunError)
|
||||||
|
require.Equal(t, int64(5), updated.Events[0].TimeMS)
|
||||||
|
require.Equal(t, "new stdout\n", textEventText(t, updated.Events[0]))
|
||||||
|
require.Equal(t, int64(6), updated.Events[1].TimeMS)
|
||||||
|
require.Equal(t, "new stderr\n", textEventText(t, updated.Events[1]))
|
||||||
|
require.Equal(t, &newErr, exitEventRunError(t, updated.Events[2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRewriteSessionFixtureReplacesChangedChunkCount verifies that bless mode
|
||||||
|
// collapses mismatched write counts to one text event to keep fixture diffs
|
||||||
|
// small.
|
||||||
|
func TestRewriteSessionFixtureReplacesChangedChunkCount(t *testing.T) {
|
||||||
|
fixture := sessionFile{
|
||||||
|
Events: []sessionEvent{
|
||||||
|
textEvent(t, 10, eventStdout, "old "),
|
||||||
|
textEvent(t, 11, eventStdout, "text\n"),
|
||||||
|
exitEvent(t, 12, nil),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, changed, err := rewriteSessionFixture(fixture,
|
||||||
|
replayedSessionOutput{
|
||||||
|
stdout: "new output\n",
|
||||||
|
stdoutChunks: []string{"new ", "output", "\n"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, changed)
|
||||||
|
require.Len(t, updated.Events, 2)
|
||||||
|
require.Equal(t, "new output\n", textEventText(t, updated.Events[0]))
|
||||||
|
require.Equal(t, int64(10), updated.Events[0].TimeMS)
|
||||||
|
require.Equal(t, eventExit, updated.Events[1].Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRewriteSessionFixtureSkipsNormalizedTimestampNoise verifies that bless
|
||||||
|
// mode does not churn fixtures when only timestamp formatting differs.
|
||||||
|
func TestRewriteSessionFixtureSkipsNormalizedTimestampNoise(t *testing.T) {
|
||||||
|
fixture := sessionFile{
|
||||||
|
Events: []sessionEvent{
|
||||||
|
textEvent(t, 3, eventStdout,
|
||||||
|
"2026-05-04T10:00:00-05:00\n"),
|
||||||
|
exitEvent(t, 4, nil),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, changed, err := rewriteSessionFixture(fixture,
|
||||||
|
replayedSessionOutput{
|
||||||
|
stdout: "2026-05-04T15:00:00Z\n",
|
||||||
|
stdoutChunks: []string{"2026-05-04T15:00:00Z\n"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, changed)
|
||||||
|
require.Equal(t, fixture, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// textEvent builds a text session event for tests.
|
||||||
|
func textEvent(t *testing.T, timeMS int64, kind, text string) sessionEvent {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
data, err := marshalSessionJSON(newTextPayload(text))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return sessionEvent{
|
||||||
|
TimeMS: timeMS,
|
||||||
|
Kind: kind,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitEvent builds an exit session event for tests.
|
||||||
|
func exitEvent(t *testing.T, timeMS int64, runError *string) sessionEvent {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
data, err := marshalSessionJSON(exitPayload{
|
||||||
|
RunError: cloneOptionalString(runError),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return sessionEvent{
|
||||||
|
TimeMS: timeMS,
|
||||||
|
Kind: eventExit,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// textEventText decodes a text payload for assertions.
|
||||||
|
func textEventText(t *testing.T, event sessionEvent) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var payload textPayload
|
||||||
|
require.NoError(t, json.Unmarshal(event.Data, &payload))
|
||||||
|
|
||||||
|
return payload.text()
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitEventRunError decodes an exit payload for assertions.
|
||||||
|
func exitEventRunError(t *testing.T, event sessionEvent) *string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var payload exitPayload
|
||||||
|
require.NoError(t, json.Unmarshal(event.Data, &payload))
|
||||||
|
|
||||||
|
return payload.RunError
|
||||||
|
}
|
||||||
712
cmd/loop/session_recorder.go
Normal file
712
cmd/loop/session_recorder.go
Normal file
|
|
@ -0,0 +1,712 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightninglabs/loop"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
statuspb "google.golang.org/genproto/googleapis/rpc/status"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
grpcstatus "google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sessionEnvVar = "LOOP_SESSION_RECORD"
|
||||||
|
sessionDefaultDir = "cmd/loop/testdata/sessions"
|
||||||
|
sessionFileExt = ".json"
|
||||||
|
defaultSessionSlug = "session"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event identifiers in recorded sessions.
|
||||||
|
const (
|
||||||
|
eventStdout = "stdout"
|
||||||
|
eventStderr = "stderr"
|
||||||
|
eventStdin = "stdin"
|
||||||
|
eventGrpc = "grpc"
|
||||||
|
eventExit = "exit"
|
||||||
|
eventSignal = "signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// grpcMarshalOptions is marshalling options to encode gRPC messages in recorded
|
||||||
|
// sessions.
|
||||||
|
var grpcMarshalOptions = protojson.MarshalOptions{
|
||||||
|
UseProtoNames: true,
|
||||||
|
EmitUnpopulated: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalSessionJSON encodes nested session payloads without HTML escaping so
|
||||||
|
// recorded fixture strings stay byte-for-byte close to the CLI text.
|
||||||
|
func marshalSessionJSON(value any) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(&buf)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
if err := encoder.Encode(value); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.TrimSuffix(buf.Bytes(), []byte("\n")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionRecorder captures CLI IO and gRPC traffic for replay.
|
||||||
|
type sessionRecorder struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
started time.Time
|
||||||
|
filePath string
|
||||||
|
slug string
|
||||||
|
|
||||||
|
metadata sessionMetadata
|
||||||
|
events []sessionEvent
|
||||||
|
eventErr error
|
||||||
|
|
||||||
|
finalizeOnce sync.Once
|
||||||
|
|
||||||
|
hooksMu sync.Mutex
|
||||||
|
hooksStarted bool
|
||||||
|
stdoutUnhook func() error
|
||||||
|
stderrUnhook func() error
|
||||||
|
stdinUnhook func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionFile is a single recorded CLI session in JSON format. They are
|
||||||
|
// stored inside sessionDefaultDir.
|
||||||
|
type sessionFile struct {
|
||||||
|
Metadata sessionMetadata `json:"metadata"`
|
||||||
|
Events []sessionEvent `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionMetadata stores static session details and runtime metadata.
|
||||||
|
type sessionMetadata struct {
|
||||||
|
Args []string `json:"args"`
|
||||||
|
Env map[string]string `json:"env"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
RunError *string `json:"run_error,omitempty"`
|
||||||
|
Duration *time.Duration `json:"duration,omitempty"`
|
||||||
|
ClockStartUnix int64 `json:"clock_start_unix"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionEvent records a single timestamped payload entry.
|
||||||
|
type sessionEvent struct {
|
||||||
|
TimeMS int64 `json:"time_ms"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// textPayload records stdout/stderr text chunks as newline-preserving lines.
|
||||||
|
type textPayload struct {
|
||||||
|
Lines []string `json:"lines"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTextPayload splits text into newline-preserving lines for fixture output.
|
||||||
|
func newTextPayload(text string) textPayload {
|
||||||
|
return textPayload{
|
||||||
|
Lines: splitTextLines(text),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// text joins a recorded text payload back into the original output.
|
||||||
|
func (p textPayload) text() string {
|
||||||
|
return joinTextLines(p.Lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stdinPayload records stdin chunks.
|
||||||
|
type stdinPayload struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitTextLines preserves line endings while splitting text for fixture
|
||||||
|
// storage.
|
||||||
|
func splitTextLines(text string) []string {
|
||||||
|
if text == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.SplitAfter(text, "\n")
|
||||||
|
if lines[len(lines)-1] == "" {
|
||||||
|
lines = lines[:len(lines)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
// joinTextLines reconstructs text stored as newline-preserving lines.
|
||||||
|
func joinTextLines(lines []string) string {
|
||||||
|
return strings.Join(lines, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// grpcPayload records a gRPC message or error event.
|
||||||
|
type grpcPayload struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
Event string `json:"event"`
|
||||||
|
MessageType string `json:"message_type,omitempty"`
|
||||||
|
Payload json.RawMessage `json:"payload,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
Status *statuspb.Status `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitPayload records the final run error, if any.
|
||||||
|
type exitPayload struct {
|
||||||
|
RunError *string `json:"run_error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// signalPayload records handled signals.
|
||||||
|
type signalPayload struct {
|
||||||
|
Signal string `json:"signal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSessionRecorder creates a recorder when session recording is enabled.
|
||||||
|
func newSessionRecorder(args []string) (*sessionRecorder, error) {
|
||||||
|
// Session recording is disabled unless the env var is set.
|
||||||
|
envValue, ok := os.LookupEnv(sessionEnvVar)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow explicit disable by setting the env var to false.
|
||||||
|
enabled, err := strconv.ParseBool(envValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid %s value %q", sessionEnvVar,
|
||||||
|
envValue)
|
||||||
|
}
|
||||||
|
if !enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the recorder before collecting metadata.
|
||||||
|
recorder := &sessionRecorder{
|
||||||
|
started: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the slug before resolving the output file path.
|
||||||
|
recorder.slug = deriveSessionSlug(args)
|
||||||
|
|
||||||
|
// Capture metadata that remains stable for the session.
|
||||||
|
metadata := sessionMetadata{
|
||||||
|
Args: append([]string(nil), args...),
|
||||||
|
Env: collectSessionEnv(),
|
||||||
|
Version: loop.RichVersion(),
|
||||||
|
ClockStartUnix: recorder.started.Unix(),
|
||||||
|
}
|
||||||
|
recorder.metadata = metadata
|
||||||
|
|
||||||
|
// Resolve the session file location.
|
||||||
|
baseDir, fileName, err := recorder.resolveFilePath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
recorder.filePath = filepath.Join(baseDir, fileName)
|
||||||
|
|
||||||
|
return recorder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectSessionEnv extracts the environment variables recorded in sessions.
|
||||||
|
func collectSessionEnv() map[string]string {
|
||||||
|
env := make(map[string]string)
|
||||||
|
|
||||||
|
// Record only LOOPCLI_ variables, excluding the recording toggle.
|
||||||
|
for _, kv := range os.Environ() {
|
||||||
|
parts := strings.SplitN(kv, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := parts[0]
|
||||||
|
value := parts[1]
|
||||||
|
if key == sessionEnvVar {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(key, "LOOPCLI_") {
|
||||||
|
env[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveFilePath chooses the output directory and filename.
|
||||||
|
func (r *sessionRecorder) resolveFilePath() (string, string, error) {
|
||||||
|
if err := ensureSessionBaseDir(sessionDefaultDir); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
counter, err := nextSessionCounter(sessionDefaultDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
slug := r.slug
|
||||||
|
if slug == "" {
|
||||||
|
slug = defaultSessionSlug
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fmt.Sprintf("%02d_%s%s", counter, slug, sessionFileExt)
|
||||||
|
|
||||||
|
return sessionDefaultDir, name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureSessionBaseDir verifies that recording is running from the repo root.
|
||||||
|
func ensureSessionBaseDir(baseDir string) error {
|
||||||
|
info, err := os.Stat(baseDir)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return fmt.Errorf("%s does not exist; run session recording "+
|
||||||
|
"from the repository root", baseDir)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
return fmt.Errorf("%s is not a directory", baseDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// logEvent records a new event with the elapsed timestamp.
|
||||||
|
func (r *sessionRecorder) logEvent(kind string, payload any) {
|
||||||
|
data, err := marshalSessionJSON(payload)
|
||||||
|
if err != nil {
|
||||||
|
r.mu.Lock()
|
||||||
|
if r.eventErr == nil {
|
||||||
|
r.eventErr = fmt.Errorf("marshal session %s event: %w",
|
||||||
|
kind, err)
|
||||||
|
}
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event := sessionEvent{
|
||||||
|
TimeMS: time.Since(r.started).Milliseconds(),
|
||||||
|
Kind: kind,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
r.events = append(r.events, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start attaches stdin/stdout/stderr hooks for session recording.
|
||||||
|
func (r *sessionRecorder) Start(stdinSource io.Reader,
|
||||||
|
stdoutForward, stderrForward io.Writer) error {
|
||||||
|
|
||||||
|
r.hooksMu.Lock()
|
||||||
|
defer r.hooksMu.Unlock()
|
||||||
|
|
||||||
|
if r.hooksStarted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture stdout and stderr first, then stdin.
|
||||||
|
origStdout := os.Stdout
|
||||||
|
if stdoutForward == nil {
|
||||||
|
stdoutForward = origStdout
|
||||||
|
}
|
||||||
|
outHook, err := hookStdout(origStdout, stdoutForward, func(p []byte) {
|
||||||
|
r.logEvent(eventStdout, newTextPayload(string(p)))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
origStderr := os.Stderr
|
||||||
|
if stderrForward == nil {
|
||||||
|
stderrForward = origStderr
|
||||||
|
}
|
||||||
|
errHook, err := hookStderr(origStderr, stderrForward, func(p []byte) {
|
||||||
|
r.logEvent(eventStderr, newTextPayload(string(p)))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_ = outHook()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
origStdin := os.Stdin
|
||||||
|
if stdinSource == nil {
|
||||||
|
stdinSource = origStdin
|
||||||
|
}
|
||||||
|
stdinHook, err := hookStdin(origStdin, stdinSource, func(p []byte) {
|
||||||
|
r.logEvent(eventStdin, stdinPayload{Text: string(p)})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_ = errHook()
|
||||||
|
_ = outHook()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.stdoutUnhook = outHook
|
||||||
|
r.stderrUnhook = errHook
|
||||||
|
r.stdinUnhook = stdinHook
|
||||||
|
r.hooksStarted = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopHooks detaches any active IO hooks.
|
||||||
|
func (r *sessionRecorder) stopHooks() error {
|
||||||
|
r.hooksMu.Lock()
|
||||||
|
defer r.hooksMu.Unlock()
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
if r.stdoutUnhook != nil {
|
||||||
|
if err := r.stdoutUnhook(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
r.stdoutUnhook = nil
|
||||||
|
}
|
||||||
|
if r.stderrUnhook != nil {
|
||||||
|
if err := r.stderrUnhook(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
r.stderrUnhook = nil
|
||||||
|
}
|
||||||
|
if r.stdinUnhook != nil {
|
||||||
|
if err := r.stdinUnhook(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
r.stdinUnhook = nil
|
||||||
|
}
|
||||||
|
r.hooksStarted = false
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// logExit records the final outcome and duration.
|
||||||
|
func (r *sessionRecorder) logExit(runErr error) {
|
||||||
|
var payload exitPayload
|
||||||
|
if runErr != nil {
|
||||||
|
msg := runErr.Error()
|
||||||
|
payload.RunError = &msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the exit event first for the event stream.
|
||||||
|
r.logEvent(eventExit, payload)
|
||||||
|
|
||||||
|
// Update metadata with the final run state.
|
||||||
|
duration := time.Since(r.started)
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
if runErr != nil {
|
||||||
|
msg := runErr.Error()
|
||||||
|
r.metadata.RunError = &msg
|
||||||
|
} else {
|
||||||
|
r.metadata.RunError = nil
|
||||||
|
}
|
||||||
|
r.metadata.Duration = &duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalize writes the recorded session to disk once.
|
||||||
|
func (r *sessionRecorder) finalize(runErr error) error {
|
||||||
|
var finalizeErr error
|
||||||
|
r.finalizeOnce.Do(func() {
|
||||||
|
hookErr := r.stopHooks()
|
||||||
|
|
||||||
|
r.logExit(runErr)
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
metadata := r.metadata
|
||||||
|
events := append([]sessionEvent(nil), r.events...)
|
||||||
|
eventErr := r.eventErr
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
fileContent := sessionFile{
|
||||||
|
Metadata: metadata,
|
||||||
|
Events: events,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll(filepath.Dir(r.filePath), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
finalizeErr = errors.Join(err, eventErr, hookErr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(r.filePath)
|
||||||
|
if err != nil {
|
||||||
|
finalizeErr = errors.Join(err, eventErr, hookErr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(file)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
if err := encoder.Encode(fileContent); err != nil {
|
||||||
|
finalizeErr = errors.Join(err, eventErr, hookErr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
finalizeErr = errors.Join(eventErr, hookErr)
|
||||||
|
})
|
||||||
|
|
||||||
|
return finalizeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize records the exit event and flushes the session to disk.
|
||||||
|
func (r *sessionRecorder) Finalize(runErr error) error {
|
||||||
|
return r.finalize(runErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockStart returns the fixed session clock used while recording CLI actions.
|
||||||
|
func (r *sessionRecorder) ClockStart() time.Time {
|
||||||
|
return time.Unix(r.metadata.ClockStartUnix, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial uses the direct gRPC connection for recording.
|
||||||
|
func (r *sessionRecorder) Dial(cmd *cli.Command) (daemonConn, func(), error) {
|
||||||
|
return dialDirectConn(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnaryInterceptor captures unary RPCs for session playback.
|
||||||
|
func (r *sessionRecorder) UnaryInterceptor() grpc.UnaryClientInterceptor {
|
||||||
|
return func(ctx context.Context, method string, req, reply any,
|
||||||
|
cc *grpc.ClientConn, invoker grpc.UnaryInvoker,
|
||||||
|
opts ...grpc.CallOption) error {
|
||||||
|
|
||||||
|
r.logGRPCMessage(method, "request", req, nil)
|
||||||
|
|
||||||
|
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||||
|
if err != nil {
|
||||||
|
r.logGRPCMessage(method, "error", nil, err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logGRPCMessage(method, "response", reply, nil)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamInterceptor captures stream RPCs for session playback.
|
||||||
|
func (r *sessionRecorder) StreamInterceptor() grpc.StreamClientInterceptor {
|
||||||
|
return func(ctx context.Context, desc *grpc.StreamDesc,
|
||||||
|
cc *grpc.ClientConn, method string, streamer grpc.Streamer,
|
||||||
|
opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||||
|
|
||||||
|
clientStream, err := streamer(ctx, desc, cc, method, opts...)
|
||||||
|
if err != nil {
|
||||||
|
r.logGRPCMessage(method, "error", nil, err)
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &recordingClientStream{
|
||||||
|
ClientStream: clientStream,
|
||||||
|
recorder: r,
|
||||||
|
method: method,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recordingClientStream wraps a gRPC stream and logs message events.
|
||||||
|
type recordingClientStream struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
|
||||||
|
recorder *sessionRecorder
|
||||||
|
method string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMsg records the outgoing stream message.
|
||||||
|
func (s *recordingClientStream) SendMsg(m any) error {
|
||||||
|
s.recorder.logGRPCMessage(s.method, "send", m, nil)
|
||||||
|
err := s.ClientStream.SendMsg(m)
|
||||||
|
if err != nil {
|
||||||
|
s.recorder.logGRPCMessage(s.method, "error", nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecvMsg records the incoming stream message.
|
||||||
|
func (s *recordingClientStream) RecvMsg(m any) error {
|
||||||
|
err := s.ClientStream.RecvMsg(m)
|
||||||
|
if err != nil {
|
||||||
|
s.recorder.logGRPCMessage(s.method, "error", nil, err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.recorder.logGRPCMessage(s.method, "recv", m, nil)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// logGRPCMessage captures gRPC request/response data in the event stream.
|
||||||
|
func (r *sessionRecorder) logGRPCMessage(method, event string, msg any,
|
||||||
|
receptionErr error) {
|
||||||
|
|
||||||
|
payload := grpcPayload{Method: method, Event: event}
|
||||||
|
|
||||||
|
if receptionErr != nil {
|
||||||
|
payload.Error = receptionErr.Error()
|
||||||
|
if grpcStatus, ok := grpcstatus.FromError(receptionErr); ok {
|
||||||
|
payload.Status = grpcStatus.Proto()
|
||||||
|
}
|
||||||
|
r.logEvent(eventGrpc, payload)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg != nil {
|
||||||
|
if protoMsg, ok := msg.(proto.Message); ok {
|
||||||
|
payload.MessageType = string(
|
||||||
|
proto.MessageName(protoMsg),
|
||||||
|
)
|
||||||
|
data, err := grpcMarshalOptions.Marshal(protoMsg)
|
||||||
|
if err == nil {
|
||||||
|
payload.Payload = data
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data, err := marshalSessionJSON(msg)
|
||||||
|
if err == nil {
|
||||||
|
payload.Payload = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.logEvent(eventGrpc, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogSignal records an incoming signal event.
|
||||||
|
func (r *sessionRecorder) LogSignal(sig os.Signal) {
|
||||||
|
r.logEvent(eventSignal, signalPayload{Signal: sig.String()})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InjectContext tags the outgoing context with the session name.
|
||||||
|
func (r *sessionRecorder) InjectContext(ctx context.Context) context.Context {
|
||||||
|
return metadata.AppendToOutgoingContext(
|
||||||
|
ctx, "loop-session", filepath.Base(r.filePath),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deriveSessionSlug builds a stable slug from the command arguments.
|
||||||
|
func deriveSessionSlug(args []string) string {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
base := filepath.Base(args[0])
|
||||||
|
tokens := []string{base}
|
||||||
|
|
||||||
|
for _, arg := range args[1:] {
|
||||||
|
if strings.HasPrefix(arg, "-") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if arg == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tokens = append(tokens, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitizeSlug(strings.Join(tokens, "-"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitizeSlug normalizes a session slug to a safe filename.
|
||||||
|
func sanitizeSlug(value string) string {
|
||||||
|
value = strings.ToLower(value)
|
||||||
|
var builder strings.Builder
|
||||||
|
lastDash := false
|
||||||
|
for _, r := range value {
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
||||||
|
builder.WriteRune(r)
|
||||||
|
lastDash = false
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lastDash {
|
||||||
|
builder.WriteRune('-')
|
||||||
|
lastDash = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slug := strings.Trim(builder.String(), "-")
|
||||||
|
if slug == "" {
|
||||||
|
return defaultSessionSlug
|
||||||
|
}
|
||||||
|
|
||||||
|
return slug
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextSessionCounter finds the next available session number.
|
||||||
|
func nextSessionCounter(baseDir string) (int, error) {
|
||||||
|
maxCounter := 0
|
||||||
|
entries, err := os.ReadDir(baseDir)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filepath.Ext(entry.Name()) != sessionFileExt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
counter, ok := parseSessionCounter(entry.Name())
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if counter > maxCounter {
|
||||||
|
maxCounter = counter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxCounter + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSessionCounter extracts the numeric prefix from a session filename.
|
||||||
|
func parseSessionCounter(name string) (int, bool) {
|
||||||
|
base := strings.TrimSuffix(name, filepath.Ext(name))
|
||||||
|
if base == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(base, "_", 2)
|
||||||
|
if parts[0] == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range parts[0] {
|
||||||
|
if r < '0' || r > '9' {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.Atoi(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if value < 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
343
cmd/loop/session_recorder_test.go
Normal file
343
cmd/loop/session_recorder_test.go
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
grpcstatus "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testLoopArg = "loop"
|
||||||
|
testOutArg = "out"
|
||||||
|
testLoopOutSlug = "loop-out"
|
||||||
|
testVersion = "test-version"
|
||||||
|
testClockStart = int64(1769407086)
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDeriveSessionSlug verifies slug derivation from CLI arguments.
|
||||||
|
func TestDeriveSessionSlug(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty_args",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "binary_only",
|
||||||
|
args: []string{"/usr/local/bin/loop"},
|
||||||
|
want: testLoopArg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with_subcommand",
|
||||||
|
args: []string{testLoopArg, testOutArg},
|
||||||
|
want: testLoopOutSlug,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "stops_at_flag",
|
||||||
|
args: []string{testLoopArg, testOutArg, "--network", "regtest"},
|
||||||
|
want: testLoopOutSlug,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skips_empty_args",
|
||||||
|
args: []string{testLoopArg, "", "quote", testOutArg},
|
||||||
|
want: "loop-quote-out",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sanitizes_tokens",
|
||||||
|
args: []string{testLoopArg, "Quote", "Out"},
|
||||||
|
want: "loop-quote-out",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sanitizes_path_base",
|
||||||
|
args: []string{"/tmp/loop-cli", testOutArg},
|
||||||
|
want: "loop-cli-out",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
slug := deriveSessionSlug(test.args)
|
||||||
|
require.Equal(t, test.want, slug)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSanitizeSlug verifies slug normalization behavior.
|
||||||
|
func TestSanitizeSlug(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "already_clean",
|
||||||
|
input: testLoopOutSlug,
|
||||||
|
want: testLoopOutSlug,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "upper_and_spaces",
|
||||||
|
input: "Loop Out",
|
||||||
|
want: testLoopOutSlug,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "symbols_collapsed",
|
||||||
|
input: "loop@@@out",
|
||||||
|
want: testLoopOutSlug,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trims_dashes",
|
||||||
|
input: "--loop-out--",
|
||||||
|
want: testLoopOutSlug,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_to_default",
|
||||||
|
input: "!!!",
|
||||||
|
want: defaultSessionSlug,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
slug := sanitizeSlug(test.input)
|
||||||
|
require.Equal(t, test.want, slug)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParseSessionCounter verifies session counter parsing.
|
||||||
|
func TestParseSessionCounter(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with_suffix",
|
||||||
|
input: "01_loop-out.json",
|
||||||
|
want: 1,
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with_extra_underscores",
|
||||||
|
input: "12_loop_in.json",
|
||||||
|
want: 12,
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no_suffix",
|
||||||
|
input: "99",
|
||||||
|
want: 99,
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no_extension",
|
||||||
|
input: "7_loop-out",
|
||||||
|
want: 7,
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_name",
|
||||||
|
input: "",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing_prefix",
|
||||||
|
input: "_loop.json",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non_numeric_prefix",
|
||||||
|
input: "loop.json",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed_prefix",
|
||||||
|
input: "1a_loop.json",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative_prefix",
|
||||||
|
input: "-1_loop.json",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "plus_prefix",
|
||||||
|
input: "+1_loop.json",
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
value, ok := parseSessionCounter(test.input)
|
||||||
|
require.Equal(t, test.ok, ok)
|
||||||
|
if test.ok {
|
||||||
|
require.Equal(t, test.want, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSessionFinalizeWritesFileWhenHookRestoreFails verifies that session
|
||||||
|
// data is still written to disk when hook restoration returns an error.
|
||||||
|
func TestSessionFinalizeWritesFileWhenHookRestoreFails(t *testing.T) {
|
||||||
|
recorder := &sessionRecorder{
|
||||||
|
started: time.Now().Add(-time.Second),
|
||||||
|
filePath: filepath.Join(t.TempDir(), "session.json"),
|
||||||
|
metadata: sessionMetadata{
|
||||||
|
Args: []string{testLoopArg, testOutArg},
|
||||||
|
Env: map[string]string{},
|
||||||
|
Version: testVersion,
|
||||||
|
ClockStartUnix: testClockStart,
|
||||||
|
},
|
||||||
|
hooksStarted: true,
|
||||||
|
stdoutUnhook: func() error {
|
||||||
|
return errors.New("stdout restore failed")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := recorder.Finalize(nil)
|
||||||
|
require.EqualError(t, err, "stdout restore failed")
|
||||||
|
|
||||||
|
blob, err := os.ReadFile(recorder.filePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var data sessionFile
|
||||||
|
require.NoError(t, json.Unmarshal(blob, &data))
|
||||||
|
require.Equal(t, recorder.metadata.Args, data.Metadata.Args)
|
||||||
|
require.Equal(t, recorder.metadata.Version, data.Metadata.Version)
|
||||||
|
require.NotNil(t, data.Metadata.Duration)
|
||||||
|
require.Nil(t, data.Metadata.RunError)
|
||||||
|
require.Len(t, data.Events, 1)
|
||||||
|
require.Equal(t, eventExit, data.Events[0].Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSessionFinalizeReportsWriteAndHookErrors verifies that a write failure
|
||||||
|
// and hook restoration failure are both returned.
|
||||||
|
func TestSessionFinalizeReportsWriteAndHookErrors(t *testing.T) {
|
||||||
|
recorder := &sessionRecorder{
|
||||||
|
started: time.Now().Add(-time.Second),
|
||||||
|
filePath: t.TempDir(),
|
||||||
|
metadata: sessionMetadata{
|
||||||
|
Args: []string{testLoopArg, testOutArg},
|
||||||
|
Env: map[string]string{},
|
||||||
|
Version: testVersion,
|
||||||
|
ClockStartUnix: testClockStart,
|
||||||
|
},
|
||||||
|
hooksStarted: true,
|
||||||
|
stdoutUnhook: func() error {
|
||||||
|
return errors.New("stdout restore failed")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := recorder.Finalize(nil)
|
||||||
|
require.ErrorContains(t, err, "is a directory")
|
||||||
|
require.ErrorContains(t, err, "stdout restore failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSessionFinalizeReportsDeferredMarshalError verifies that marshal
|
||||||
|
// failures encountered while recording are reported from Finalize while the
|
||||||
|
// session file is still written to disk.
|
||||||
|
func TestSessionFinalizeReportsDeferredMarshalError(t *testing.T) {
|
||||||
|
recorder := &sessionRecorder{
|
||||||
|
started: time.Now().Add(-time.Second),
|
||||||
|
filePath: filepath.Join(t.TempDir(), "session.json"),
|
||||||
|
metadata: sessionMetadata{
|
||||||
|
Args: []string{testLoopArg, testOutArg},
|
||||||
|
Env: map[string]string{},
|
||||||
|
Version: testVersion,
|
||||||
|
ClockStartUnix: testClockStart,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// This logEvent will fail, because chan int is not JSON marshallable.
|
||||||
|
recorder.logEvent(eventStdout, make(chan int))
|
||||||
|
|
||||||
|
err := recorder.Finalize(nil)
|
||||||
|
require.ErrorContains(t, err, "marshal session stdout event")
|
||||||
|
require.ErrorContains(t, err, "unsupported type: chan int")
|
||||||
|
|
||||||
|
blob, err := os.ReadFile(recorder.filePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var data sessionFile
|
||||||
|
require.NoError(t, json.Unmarshal(blob, &data))
|
||||||
|
require.Equal(t, recorder.metadata.Args, data.Metadata.Args)
|
||||||
|
require.Equal(t, recorder.metadata.Version, data.Metadata.Version)
|
||||||
|
require.NotNil(t, data.Metadata.Duration)
|
||||||
|
require.Nil(t, data.Metadata.RunError)
|
||||||
|
require.Len(t, data.Events, 1)
|
||||||
|
require.Equal(t, eventExit, data.Events[0].Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewSessionRecorderRequiresRepoRoot verifies that recording fails clearly
|
||||||
|
// when the session fixture directory is not available from the current working
|
||||||
|
// directory.
|
||||||
|
func TestNewSessionRecorderRequiresRepoRoot(t *testing.T) {
|
||||||
|
t.Chdir(t.TempDir())
|
||||||
|
t.Setenv(sessionEnvVar, "true")
|
||||||
|
|
||||||
|
recorder, err := newSessionRecorder([]string{testLoopArg, testOutArg})
|
||||||
|
require.Nil(t, recorder)
|
||||||
|
require.EqualError(t, err, "cmd/loop/testdata/sessions does not "+
|
||||||
|
"exist; run session recording from the repository root")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewSessionRecorderCapturesClockStart verifies that new recordings store
|
||||||
|
// the clock start used for deterministic replay.
|
||||||
|
func TestNewSessionRecorderCapturesClockStart(t *testing.T) {
|
||||||
|
repoRoot := t.TempDir()
|
||||||
|
require.NoError(
|
||||||
|
t, os.MkdirAll(filepath.Join(repoRoot, sessionDefaultDir), 0o755),
|
||||||
|
)
|
||||||
|
t.Chdir(repoRoot)
|
||||||
|
t.Setenv(sessionEnvVar, "true")
|
||||||
|
|
||||||
|
before := time.Now().Unix()
|
||||||
|
recorder, err := newSessionRecorder([]string{testLoopArg, testOutArg})
|
||||||
|
after := time.Now().Unix()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, recorder)
|
||||||
|
require.GreaterOrEqual(t, recorder.metadata.ClockStartUnix, before)
|
||||||
|
require.LessOrEqual(t, recorder.metadata.ClockStartUnix, after)
|
||||||
|
require.Equal(
|
||||||
|
t, time.Unix(recorder.metadata.ClockStartUnix, 0),
|
||||||
|
recorder.ClockStart(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLogGRPCMessageRecordsStatus verifies that gRPC status errors retain
|
||||||
|
// their structured status details in recorded sessions.
|
||||||
|
func TestLogGRPCMessageRecordsStatus(t *testing.T) {
|
||||||
|
recorder := &sessionRecorder{
|
||||||
|
started: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
statusErr := grpcstatus.Error(
|
||||||
|
codes.FailedPrecondition, "no rules set",
|
||||||
|
)
|
||||||
|
recorder.logGRPCMessage(
|
||||||
|
"/looprpc.SwapClient/SuggestSwaps", "error", nil, statusErr,
|
||||||
|
)
|
||||||
|
|
||||||
|
require.Len(t, recorder.events, 1)
|
||||||
|
require.Equal(t, eventGrpc, recorder.events[0].Kind)
|
||||||
|
|
||||||
|
var payload grpcPayload
|
||||||
|
require.NoError(t, json.Unmarshal(recorder.events[0].Data, &payload))
|
||||||
|
require.Equal(t, statusErr.Error(), payload.Error)
|
||||||
|
require.NotNil(t, payload.Status)
|
||||||
|
require.Equal(t, int32(codes.FailedPrecondition), payload.Status.Code)
|
||||||
|
require.Equal(t, "no rules set", payload.Status.Message)
|
||||||
|
}
|
||||||
1392
cmd/loop/session_replay_test.go
Normal file
1392
cmd/loop/session_replay_test.go
Normal file
File diff suppressed because it is too large
Load diff
143
cmd/loop/session_stdio.go
Normal file
143
cmd/loop/session_stdio.go
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hookStdout redirects stdout and returns a function to restore it.
|
||||||
|
func hookStdout(orig *os.File, forward io.Writer,
|
||||||
|
onChunk func([]byte)) (func() error, error) {
|
||||||
|
|
||||||
|
return hookOutput(
|
||||||
|
func(f *os.File) { os.Stdout = f },
|
||||||
|
orig,
|
||||||
|
forward,
|
||||||
|
onChunk,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hookStderr redirects stderr and returns a function to restore it.
|
||||||
|
func hookStderr(orig *os.File, forward io.Writer,
|
||||||
|
onChunk func([]byte)) (func() error, error) {
|
||||||
|
|
||||||
|
return hookOutput(
|
||||||
|
func(f *os.File) { os.Stderr = f },
|
||||||
|
orig,
|
||||||
|
forward,
|
||||||
|
onChunk,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hookOutput redirects an output stream and returns a restore function.
|
||||||
|
func hookOutput(setDest func(*os.File), orig *os.File, forward io.Writer,
|
||||||
|
onChunk func([]byte)) (func() error, error) {
|
||||||
|
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
setDest(w)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Go(func() {
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
writer := composeWriter(forward, onChunk)
|
||||||
|
// Restoring the original descriptor closes the pipe from the
|
||||||
|
// other side, so io.Copy can fail as part of normal teardown.
|
||||||
|
_, _ = io.Copy(writer, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
return func() error {
|
||||||
|
setDest(orig)
|
||||||
|
_ = w.Close()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hookStdin redirects stdin and returns a function to restore it.
|
||||||
|
func hookStdin(orig *os.File, source io.Reader,
|
||||||
|
onChunk func([]byte)) (func() error, error) {
|
||||||
|
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
useOrig := false
|
||||||
|
if source == nil {
|
||||||
|
source = orig
|
||||||
|
useOrig = true
|
||||||
|
} else if source == orig {
|
||||||
|
useOrig = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if onChunk != nil {
|
||||||
|
source = io.TeeReader(source, chunkWriter(onChunk))
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Stdin = r
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Go(func() {
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
// Closing the pipe during hook restoration can terminate the
|
||||||
|
// copy loop with an expected error.
|
||||||
|
_, _ = io.Copy(w, source)
|
||||||
|
})
|
||||||
|
|
||||||
|
return func() error {
|
||||||
|
os.Stdin = orig
|
||||||
|
_ = r.Close()
|
||||||
|
_ = w.Close()
|
||||||
|
// When stdin is still backed by the original terminal, the copy
|
||||||
|
// goroutine can block indefinitely on user input. In that case
|
||||||
|
// we rely on closing the pipe or process exit to end the copy
|
||||||
|
// loop instead of waiting here and hanging the CLI shutdown
|
||||||
|
// path.
|
||||||
|
if !useOrig {
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// composeWriter builds a writer that forwards to the provided sinks.
|
||||||
|
func composeWriter(forward io.Writer, onChunk func([]byte)) io.Writer {
|
||||||
|
switch {
|
||||||
|
case forward != nil && onChunk != nil:
|
||||||
|
return io.MultiWriter(forward, chunkWriter(onChunk))
|
||||||
|
|
||||||
|
case forward != nil:
|
||||||
|
return forward
|
||||||
|
|
||||||
|
case onChunk != nil:
|
||||||
|
return chunkWriter(onChunk)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return io.Discard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// chunkWriter writes copy-safe chunks to a callback.
|
||||||
|
type chunkWriter func([]byte)
|
||||||
|
|
||||||
|
// Write copies p before forwarding to the callback.
|
||||||
|
func (w chunkWriter) Write(p []byte) (int, error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copyBuf := make([]byte, len(p))
|
||||||
|
copy(copyBuf, p)
|
||||||
|
w(copyBuf)
|
||||||
|
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
83
cmd/loop/session_transport.go
Normal file
83
cmd/loop/session_transport.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// grpcTransport customizes gRPC dialing and interceptors for sessions.
|
||||||
|
type grpcTransport interface {
|
||||||
|
// Dial returns a gRPC connection for the CLI.
|
||||||
|
Dial(cmd *cli.Command) (daemonConn, func(), error)
|
||||||
|
|
||||||
|
// UnaryInterceptor returns the unary interceptor to apply for session
|
||||||
|
// flows.
|
||||||
|
UnaryInterceptor() grpc.UnaryClientInterceptor
|
||||||
|
|
||||||
|
// StreamInterceptor returns the stream interceptor to apply for session
|
||||||
|
// flows.
|
||||||
|
StreamInterceptor() grpc.StreamClientInterceptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// daemonConn is the client connection interface required by stop/wait flows.
|
||||||
|
type daemonConn interface {
|
||||||
|
grpc.ClientConnInterface
|
||||||
|
|
||||||
|
// GetState reports the current connectivity state of the client
|
||||||
|
// channel.
|
||||||
|
GetState() connectivity.State
|
||||||
|
|
||||||
|
// WaitForStateChange blocks until the state changes or the context
|
||||||
|
// expires.
|
||||||
|
WaitForStateChange(ctx context.Context,
|
||||||
|
sourceState connectivity.State) bool
|
||||||
|
|
||||||
|
// Connect forces the channel out of idle mode.
|
||||||
|
Connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// directGrpcTransport establishes real gRPC connections to loopd.
|
||||||
|
type directGrpcTransport struct{}
|
||||||
|
|
||||||
|
// Dial opens a direct gRPC connection.
|
||||||
|
func (t *directGrpcTransport) Dial(
|
||||||
|
cmd *cli.Command) (daemonConn, func(), error) {
|
||||||
|
|
||||||
|
return dialDirectConn(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnaryInterceptor returns nil because direct connections do not need wrapping.
|
||||||
|
func (t *directGrpcTransport) UnaryInterceptor() grpc.UnaryClientInterceptor {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamInterceptor returns nil because direct connections do not need
|
||||||
|
// wrapping.
|
||||||
|
func (t *directGrpcTransport) StreamInterceptor() grpc.StreamClientInterceptor {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionTransport defines the active gRPC transport for CLI commands.
|
||||||
|
var sessionTransport grpcTransport = &directGrpcTransport{}
|
||||||
|
|
||||||
|
// hookGrpc installs the active gRPC session transport hook.
|
||||||
|
func hookGrpc(transport grpcTransport) func() {
|
||||||
|
prev := sessionTransport
|
||||||
|
sessionTransport = transport
|
||||||
|
|
||||||
|
return func() { sessionTransport = prev }
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialDirectConn returns the standard CLI gRPC connection.
|
||||||
|
func dialDirectConn(cmd *cli.Command) (daemonConn, func(), error) {
|
||||||
|
rpcServer := cmd.String("rpcserver")
|
||||||
|
tlsCertPath, macaroonPath, err := extractPathArgs(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return getClientConn(rpcServer, tlsCertPath, macaroonPath)
|
||||||
|
}
|
||||||
|
|
@ -4,14 +4,17 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/lightninglabs/loop/labels"
|
"github.com/lightninglabs/loop/labels"
|
||||||
"github.com/lightninglabs/loop/looprpc"
|
"github.com/lightninglabs/loop/looprpc"
|
||||||
"github.com/lightninglabs/loop/staticaddr/deposit"
|
|
||||||
"github.com/lightninglabs/loop/staticaddr/loopin"
|
"github.com/lightninglabs/loop/staticaddr/loopin"
|
||||||
"github.com/lightninglabs/loop/swapserverrpc"
|
"github.com/lightninglabs/loop/swapserverrpc"
|
||||||
lndcommands "github.com/lightningnetwork/lnd/cmd/commands"
|
"github.com/lightningnetwork/lnd"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
@ -192,7 +195,7 @@ func withdraw(ctx context.Context, cmd *cli.Command) error {
|
||||||
case isAllSelected:
|
case isAllSelected:
|
||||||
case isUtxoSelected:
|
case isUtxoSelected:
|
||||||
utxos := cmd.StringSlice("utxo")
|
utxos := cmd.StringSlice("utxo")
|
||||||
outpoints, err = lndcommands.UtxosToOutpoints(utxos)
|
outpoints, err = lnd.UtxosToOutpoints(utxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -349,7 +352,7 @@ func listWithdrawals(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
|
||||||
var listStaticAddressSwapsCommand = &cli.Command{
|
var listStaticAddressSwapsCommand = &cli.Command{
|
||||||
Name: "listswaps",
|
Name: "listswaps",
|
||||||
Usage: "Shows a list of finalized static address swaps.",
|
Usage: "Shows a list of static address swaps.",
|
||||||
Description: `
|
Description: `
|
||||||
`,
|
`,
|
||||||
Action: listStaticAddressSwaps,
|
Action: listStaticAddressSwaps,
|
||||||
|
|
@ -553,11 +556,7 @@ func staticAddressLoopIn(ctx context.Context, cmd *cli.Command) error {
|
||||||
allDeposits := depositList.FilteredDeposits
|
allDeposits := depositList.FilteredDeposits
|
||||||
|
|
||||||
if len(allDeposits) == 0 {
|
if len(allDeposits) == 0 {
|
||||||
errString := fmt.Sprintf("no confirmed deposits available, "+
|
return errors.New("no deposited outputs available")
|
||||||
"deposits need at least %v confirmations",
|
|
||||||
deposit.MinConfs)
|
|
||||||
|
|
||||||
return errors.New(errString)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var depositOutpoints []string
|
var depositOutpoints []string
|
||||||
|
|
@ -614,6 +613,28 @@ func staticAddressLoopIn(ctx context.Context, cmd *cli.Command) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warn the user if any selected deposits have fewer than 6
|
||||||
|
// confirmations, as the swap payment won't be received immediately
|
||||||
|
// for those.
|
||||||
|
summary, err := client.GetStaticAddressSummary(
|
||||||
|
ctx, &looprpc.StaticAddressSummaryRequest{},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
depositsToCheck := warningDepositOutpoints(
|
||||||
|
allDeposits, depositOutpoints, autoSelectDepositsForQuote,
|
||||||
|
quoteReq.Amt,
|
||||||
|
)
|
||||||
|
warning := lowConfDepositWarning(
|
||||||
|
allDeposits, depositsToCheck,
|
||||||
|
int64(summary.RelativeExpiryBlocks),
|
||||||
|
)
|
||||||
|
if warning != "" {
|
||||||
|
fmt.Println(warning)
|
||||||
|
}
|
||||||
|
|
||||||
if !(cmd.Bool("force") || cmd.Bool("f")) {
|
if !(cmd.Bool("force") || cmd.Bool("f")) {
|
||||||
err = displayInDetails(quoteReq, quote, cmd.Bool("verbose"))
|
err = displayInDetails(quoteReq, quote, cmd.Bool("verbose"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -669,6 +690,162 @@ func depositsToOutpoints(deposits []*looprpc.Deposit) []string {
|
||||||
return outpoints
|
return outpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var warningSelectionDustLimit = int64(lnwallet.DustLimitForSize(input.P2TRSize))
|
||||||
|
|
||||||
|
// warningDepositOutpoints returns the deposit outpoints to check for
|
||||||
|
// low-confirmation warnings.
|
||||||
|
func warningDepositOutpoints(allDeposits []*looprpc.Deposit,
|
||||||
|
selectedOutpoints []string, autoSelect bool, targetAmount int64) []string {
|
||||||
|
|
||||||
|
if !autoSelect {
|
||||||
|
return selectedOutpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
return autoSelectedWarningOutpoints(allDeposits, targetAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// autoSelectedWarningOutpoints returns the outpoints selected by the same
|
||||||
|
// ordering used for automatic static loop-in deposit selection.
|
||||||
|
func autoSelectedWarningOutpoints(allDeposits []*looprpc.Deposit,
|
||||||
|
targetAmount int64) []string {
|
||||||
|
|
||||||
|
if targetAmount <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KEEP IN SYNC with staticaddr/loopin.SelectDeposits.
|
||||||
|
deposits := filterSwappableWarningDeposits(allDeposits)
|
||||||
|
sort.Slice(deposits, func(i, j int) bool {
|
||||||
|
iConfirmed := deposits[i].ConfirmationHeight > 0
|
||||||
|
jConfirmed := deposits[j].ConfirmationHeight > 0
|
||||||
|
if iConfirmed != jConfirmed {
|
||||||
|
return iConfirmed
|
||||||
|
}
|
||||||
|
|
||||||
|
if deposits[i].Value == deposits[j].Value {
|
||||||
|
return deposits[i].BlocksUntilExpiry <
|
||||||
|
deposits[j].BlocksUntilExpiry
|
||||||
|
}
|
||||||
|
|
||||||
|
return deposits[i].Value > deposits[j].Value
|
||||||
|
})
|
||||||
|
|
||||||
|
selectedOutpoints := make([]string, 0, len(deposits))
|
||||||
|
var selectedAmount int64
|
||||||
|
for _, deposit := range deposits {
|
||||||
|
selectedOutpoints = append(selectedOutpoints, deposit.Outpoint)
|
||||||
|
selectedAmount += deposit.Value
|
||||||
|
if selectedAmount == targetAmount {
|
||||||
|
return selectedOutpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectedAmount > targetAmount &&
|
||||||
|
selectedAmount-targetAmount >= warningSelectionDustLimit {
|
||||||
|
|
||||||
|
return selectedOutpoints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterSwappableWarningDeposits filters deposits for CLI warning selection.
|
||||||
|
func filterSwappableWarningDeposits(
|
||||||
|
allDeposits []*looprpc.Deposit) []*looprpc.Deposit {
|
||||||
|
|
||||||
|
swappable := make([]*looprpc.Deposit, 0, len(allDeposits))
|
||||||
|
minBlocksUntilExpiry := int64(
|
||||||
|
loopin.DefaultLoopInOnChainCltvDelta + loopin.DepositHtlcDelta,
|
||||||
|
)
|
||||||
|
for _, deposit := range allDeposits {
|
||||||
|
// Unconfirmed deposits remain swappable because their CSV timeout has
|
||||||
|
// not started yet. This mirrors loopin.IsSwappable.
|
||||||
|
if deposit.ConfirmationHeight > 0 &&
|
||||||
|
deposit.BlocksUntilExpiry < minBlocksUntilExpiry {
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
swappable = append(swappable, deposit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return swappable
|
||||||
|
}
|
||||||
|
|
||||||
|
// conservativeWarningConfs is the highest default confirmation tier used by
|
||||||
|
// the server's dynamic confirmation-risk policy.
|
||||||
|
//
|
||||||
|
// The CLI does not currently know the server's exact policy, so we use this
|
||||||
|
// conservative threshold for warnings without promising immediate execution.
|
||||||
|
const conservativeWarningConfs = 6
|
||||||
|
|
||||||
|
// lowConfDepositWarning checks the selected deposits against a conservative
|
||||||
|
// confirmation threshold and returns a warning string if any are found.
|
||||||
|
func lowConfDepositWarning(allDeposits []*looprpc.Deposit,
|
||||||
|
selectedOutpoints []string, csvExpiry int64) string {
|
||||||
|
|
||||||
|
depositMap := make(map[string]*looprpc.Deposit, len(allDeposits))
|
||||||
|
for _, d := range allDeposits {
|
||||||
|
depositMap[d.Outpoint] = d
|
||||||
|
}
|
||||||
|
|
||||||
|
var lowConfEntries []string
|
||||||
|
for _, op := range selectedOutpoints {
|
||||||
|
d, ok := depositMap[op]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var confs int64
|
||||||
|
switch {
|
||||||
|
case d.ConfirmationHeight <= 0:
|
||||||
|
confs = 0
|
||||||
|
|
||||||
|
case csvExpiry > 0:
|
||||||
|
// For confirmed deposits we can compute
|
||||||
|
// confirmations as CSVExpiry - BlocksUntilExpiry + 1.
|
||||||
|
confs = csvExpiry - d.BlocksUntilExpiry + 1
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Can't determine confirmations without the CSV expiry.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if confs >= conservativeWarningConfs {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if confs == 0 {
|
||||||
|
lowConfEntries = append(
|
||||||
|
lowConfEntries,
|
||||||
|
fmt.Sprintf(" - %s (unconfirmed)", op),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
lowConfEntries = append(
|
||||||
|
lowConfEntries,
|
||||||
|
fmt.Sprintf(
|
||||||
|
" - %s (%d confirmations)", op,
|
||||||
|
confs,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lowConfEntries) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"\nWARNING: The following deposits are below the "+
|
||||||
|
"conservative %d-confirmation threshold:\n%s\n"+
|
||||||
|
"The swap payment for these deposits may wait for "+
|
||||||
|
"more confirmations depending on the server's "+
|
||||||
|
"confirmation-risk policy.\n",
|
||||||
|
conservativeWarningConfs,
|
||||||
|
strings.Join(lowConfEntries, "\n"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func displayNewAddressWarning() error {
|
func displayNewAddressWarning() error {
|
||||||
fmt.Printf("\nWARNING: Be aware that loosing your l402.token file in " +
|
fmt.Printf("\nWARNING: Be aware that loosing your l402.token file in " +
|
||||||
".loop under your home directory will take your ability to " +
|
".loop under your home directory will take your ability to " +
|
||||||
|
|
|
||||||
220
cmd/loop/staticaddr_test.go
Normal file
220
cmd/loop/staticaddr_test.go
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightninglabs/loop/looprpc"
|
||||||
|
"github.com/lightninglabs/loop/staticaddr/deposit"
|
||||||
|
"github.com/lightninglabs/loop/staticaddr/loopin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestLowConfDepositWarningConfirmedOnly verifies confirmed deposits below the
|
||||||
|
// conservative warning threshold are included in the warning text.
|
||||||
|
func TestLowConfDepositWarningConfirmedOnly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
deposits := []*looprpc.Deposit{
|
||||||
|
{
|
||||||
|
Outpoint: "confirmed-low",
|
||||||
|
ConfirmationHeight: 100,
|
||||||
|
BlocksUntilExpiry: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Outpoint: "confirmed-high",
|
||||||
|
ConfirmationHeight: 95,
|
||||||
|
BlocksUntilExpiry: 139,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
warning := lowConfDepositWarning(
|
||||||
|
deposits, []string{"confirmed-low", "confirmed-high"}, 144,
|
||||||
|
)
|
||||||
|
|
||||||
|
require.Contains(t, warning, "confirmed-low (5 confirmations)")
|
||||||
|
require.NotContains(t, warning, "confirmed-high")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLowConfDepositWarningUnconfirmed verifies unconfirmed deposits get a
|
||||||
|
// warning that the swap may wait for confirmation-risk acceptance.
|
||||||
|
func TestLowConfDepositWarningUnconfirmed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
deposits := []*looprpc.Deposit{
|
||||||
|
{
|
||||||
|
Outpoint: "mempool",
|
||||||
|
ConfirmationHeight: 0,
|
||||||
|
BlocksUntilExpiry: 144,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
warning := lowConfDepositWarning(deposits, []string{"mempool"}, 144)
|
||||||
|
|
||||||
|
require.Contains(t, warning, "mempool (unconfirmed)")
|
||||||
|
require.True(
|
||||||
|
t,
|
||||||
|
strings.Contains(
|
||||||
|
warning,
|
||||||
|
"conservative 6-confirmation threshold",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
require.NotContains(t, warning, "executed immediately")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWarningDepositOutpointsAutoSelectPrefersConfirmed verifies automatic
|
||||||
|
// warning selection keeps the loop-in preference for confirmed outputs.
|
||||||
|
func TestWarningDepositOutpointsAutoSelectPrefersConfirmed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const csvExpiry = 1100
|
||||||
|
|
||||||
|
deposits := []*looprpc.Deposit{
|
||||||
|
{
|
||||||
|
Outpoint: "mempool-large",
|
||||||
|
Value: 2_000_000,
|
||||||
|
ConfirmationHeight: 0,
|
||||||
|
BlocksUntilExpiry: csvExpiry,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Outpoint: "confirmed",
|
||||||
|
Value: 1_500_000,
|
||||||
|
ConfirmationHeight: 100,
|
||||||
|
BlocksUntilExpiry: csvExpiry - 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
selected := warningDepositOutpoints(deposits, nil, true, 1_000_000)
|
||||||
|
|
||||||
|
require.Equal(t, []string{"confirmed"}, selected)
|
||||||
|
require.Empty(t, lowConfDepositWarning(deposits, selected, csvExpiry))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWarningDepositOutpointsAutoSelectIncludesNeededUnconfirmed verifies the
|
||||||
|
// warning path includes mempool deposits when they are needed for the target.
|
||||||
|
func TestWarningDepositOutpointsAutoSelectIncludesNeededUnconfirmed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const csvExpiry = 1100
|
||||||
|
|
||||||
|
deposits := []*looprpc.Deposit{
|
||||||
|
{
|
||||||
|
Outpoint: "confirmed-small",
|
||||||
|
Value: 500_000,
|
||||||
|
ConfirmationHeight: 100,
|
||||||
|
BlocksUntilExpiry: csvExpiry - 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Outpoint: "mempool-large",
|
||||||
|
Value: 2_000_000,
|
||||||
|
ConfirmationHeight: 0,
|
||||||
|
BlocksUntilExpiry: csvExpiry,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
selected := warningDepositOutpoints(deposits, nil, true, 1_000_000)
|
||||||
|
|
||||||
|
require.Equal(
|
||||||
|
t, []string{"confirmed-small", "mempool-large"}, selected,
|
||||||
|
)
|
||||||
|
|
||||||
|
warning := lowConfDepositWarning(deposits, selected, csvExpiry)
|
||||||
|
require.Contains(t, warning, "mempool-large (unconfirmed)")
|
||||||
|
require.NotContains(t, warning, "confirmed-small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWarningDepositSelectionMatchesLoopInSelection verifies CLI warning
|
||||||
|
// selection matches the loop-in selector.
|
||||||
|
func TestWarningDepositSelectionMatchesLoopInSelection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const (
|
||||||
|
blockHeight = uint32(10_000)
|
||||||
|
csvExpiry = uint32(1_200)
|
||||||
|
targetAmount = int64(2_500_000)
|
||||||
|
)
|
||||||
|
|
||||||
|
type fixture struct {
|
||||||
|
name string
|
||||||
|
value int64
|
||||||
|
confirmationHeight int64
|
||||||
|
}
|
||||||
|
|
||||||
|
fixtures := []fixture{
|
||||||
|
{
|
||||||
|
name: "mempool-huge",
|
||||||
|
value: 3_000_000,
|
||||||
|
confirmationHeight: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "confirmed-later-expiry",
|
||||||
|
value: 2_000_000,
|
||||||
|
confirmationHeight: 9_900,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "confirmed-earlier-expiry",
|
||||||
|
value: 2_000_000,
|
||||||
|
confirmationHeight: 9_890,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "confirmed-small",
|
||||||
|
value: 600_000,
|
||||||
|
confirmationHeight: 9_900,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "confirmed-too-close-to-expiry",
|
||||||
|
value: 5_000_000,
|
||||||
|
confirmationHeight: 9_849,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcDeposits := make([]*looprpc.Deposit, 0, len(fixtures))
|
||||||
|
loopInDeposits := make([]*deposit.Deposit, 0, len(fixtures))
|
||||||
|
for idx, fixture := range fixtures {
|
||||||
|
hash := chainhash.Hash{byte(idx + 1)}
|
||||||
|
outpoint := wire.OutPoint{
|
||||||
|
Hash: hash,
|
||||||
|
Index: uint32(idx),
|
||||||
|
}
|
||||||
|
|
||||||
|
blocksUntilExpiry := int64(0)
|
||||||
|
if fixture.confirmationHeight > 0 {
|
||||||
|
blocksUntilExpiry = fixture.confirmationHeight +
|
||||||
|
int64(csvExpiry) - int64(blockHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcDeposits = append(rpcDeposits, &looprpc.Deposit{
|
||||||
|
Outpoint: outpoint.String(),
|
||||||
|
Value: fixture.value,
|
||||||
|
ConfirmationHeight: fixture.confirmationHeight,
|
||||||
|
BlocksUntilExpiry: blocksUntilExpiry,
|
||||||
|
})
|
||||||
|
loopInDeposits = append(loopInDeposits, &deposit.Deposit{
|
||||||
|
OutPoint: outpoint,
|
||||||
|
Value: btcutil.Amount(fixture.value),
|
||||||
|
ConfirmationHeight: fixture.confirmationHeight,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cliSelected := autoSelectedWarningOutpoints(
|
||||||
|
rpcDeposits, targetAmount,
|
||||||
|
)
|
||||||
|
|
||||||
|
loopInSelected, err := loopin.SelectDeposits(
|
||||||
|
btcutil.Amount(targetAmount), loopInDeposits, csvExpiry,
|
||||||
|
blockHeight,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
loopInSelectedOutpoints := make([]string, 0, len(loopInSelected))
|
||||||
|
for _, selected := range loopInSelected {
|
||||||
|
loopInSelectedOutpoints = append(
|
||||||
|
loopInSelectedOutpoints, selected.OutPoint.String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, loopInSelectedOutpoints, cliSelected)
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
"github.com/lightninglabs/loop/looprpc"
|
"github.com/lightninglabs/loop/looprpc"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/connectivity"
|
"google.golang.org/grpc/connectivity"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -63,7 +62,7 @@ func stopDaemon(ctx context.Context, cmd *cli.Command) error {
|
||||||
// waitForDaemonShutdown monitors the gRPC connectivity state until the daemon
|
// waitForDaemonShutdown monitors the gRPC connectivity state until the daemon
|
||||||
// disappears. To avoid getting stuck in idle mode we nudge the connection to
|
// disappears. To avoid getting stuck in idle mode we nudge the connection to
|
||||||
// reconnect when needed.
|
// reconnect when needed.
|
||||||
func waitForDaemonShutdown(ctx context.Context, conn *grpc.ClientConn) error {
|
func waitForDaemonShutdown(ctx context.Context, conn daemonConn) error {
|
||||||
for {
|
for {
|
||||||
state := conn.GetState()
|
state := conn.GetState()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,10 @@ import (
|
||||||
|
|
||||||
var listSwapsCommand = &cli.Command{
|
var listSwapsCommand = &cli.Command{
|
||||||
Name: "listswaps",
|
Name: "listswaps",
|
||||||
Usage: "list all swaps in the local database",
|
Usage: "list traditional Loop In and Loop Out swaps",
|
||||||
Description: "Allows the user to get a list of all swaps that are " +
|
Description: "Lists traditional Loop In and Loop Out swaps that are " +
|
||||||
"currently stored in the database",
|
"currently stored in the database. Static address loop-ins are not " +
|
||||||
|
"included; use `loop static listswaps` to view them.",
|
||||||
Action: listSwaps,
|
Action: listSwaps,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
|
|
@ -128,10 +129,12 @@ func listSwaps(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
|
||||||
var swapInfoCommand = &cli.Command{
|
var swapInfoCommand = &cli.Command{
|
||||||
Name: "swapinfo",
|
Name: "swapinfo",
|
||||||
Usage: "show the status of a swap",
|
Usage: "show the status of a traditional swap",
|
||||||
ArgsUsage: "id",
|
ArgsUsage: "id",
|
||||||
Description: "Allows the user to get the status of a single swap " +
|
Description: "Shows the status of a traditional Loop In or Loop Out " +
|
||||||
"currently stored in the database",
|
"swap currently stored in the database. Static address loop-ins " +
|
||||||
|
"must be viewed with `loop static listswaps`; there is no generic " +
|
||||||
|
"per-swap static lookup command.",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.Uint64Flag{
|
&cli.Uint64Flag{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
|
|
@ -206,9 +209,6 @@ func abandonSwap(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
|
||||||
var id string
|
var id string
|
||||||
switch {
|
switch {
|
||||||
case cmd.IsSet("id"):
|
|
||||||
id = cmd.String("id")
|
|
||||||
|
|
||||||
case cmd.NArg() > 0:
|
case cmd.NArg() > 0:
|
||||||
id = args.First()
|
id = args.First()
|
||||||
|
|
||||||
|
|
|
||||||
81
cmd/loop/testdata/sessions/AGENTS.md
vendored
Normal file
81
cmd/loop/testdata/sessions/AGENTS.md
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# Session Recording Notes (Loop CLI)
|
||||||
|
|
||||||
|
## How to record sessions
|
||||||
|
- Use the local CLI binary: `/home/user/bin/loop`.
|
||||||
|
- Always include `--network regtest` **after** the main command and subcommands so it does not become part of the session filename.
|
||||||
|
- Record with `LOOP_SESSION_RECORD=true`, e.g.:
|
||||||
|
- `LOOP_SESSION_RECORD=true /home/user/bin/loop quote out --network regtest 500000`
|
||||||
|
- After recording several related commands, group the resulting JSON files into a subdir under `cmd/loop/testdata/sessions/`.
|
||||||
|
|
||||||
|
## Control panel HTTP server (regtest helpers)
|
||||||
|
Base URL: `http://127.0.0.1:12345`
|
||||||
|
- Implementation: [httpcmd](https://github.com/starius/httpcmd/)
|
||||||
|
- Config: [control panel config](https://gist.github.com/starius/6604ffe27f51d55f4cf715b4202637dd)
|
||||||
|
- `/mine`:
|
||||||
|
- Mines a block. Returns JSON with `exit_code`, `stdout`, `stderr`, `duration`, `timed_out`.
|
||||||
|
- `/deposit`:
|
||||||
|
- Sends a deposit to the client static address. Returns JSON with a `txid` on success.
|
||||||
|
- May fail with insufficient funds unless blocks have been mined first.
|
||||||
|
- `/reservation`:
|
||||||
|
- Opens a reservation for instant out. Returns JSON with reservation details (id, amount, state, etc.).
|
||||||
|
- `/loop-log`:
|
||||||
|
- Returns loopd logs (text). Use a tail filter when inspecting.
|
||||||
|
- `/loop-server-log`:
|
||||||
|
- Returns loop server logs (text). Use a tail filter when inspecting.
|
||||||
|
- Tip: The response is JSON with a large `stdout` field; extract first, then tail:
|
||||||
|
- `curl -s http://127.0.0.1:12345/loop-server-log | jq -r '.stdout' > /tmp/loop-server-log.txt`
|
||||||
|
- `tail -n 50 /tmp/loop-server-log.txt`
|
||||||
|
|
||||||
|
## Useful regtest flow observations
|
||||||
|
- Deposits may be unconfirmed for a few blocks; mine multiple blocks to move a deposit into `DEPOSITED`.
|
||||||
|
- After making a deposit, `loop static summary` reflects unconfirmed + confirmed values; `loop static listdeposits` lists confirmed deposits.
|
||||||
|
- Reservations should be opened (via `/reservation`) before `instantout`; list them with `loop reservations list`.
|
||||||
|
- `instantout` uses interactive selection; `ALL` then `y` works for a simple scenario:
|
||||||
|
- `printf "ALL\ny\n" | LOOP_SESSION_RECORD=true /home/user/bin/loop instantout --network regtest`
|
||||||
|
|
||||||
|
## Coverage notes (high-level)
|
||||||
|
- Sessions were recorded for: terms, getinfo, quote in/out, listauth, fetchl402, getparams, setrule (error + success), suggestswaps (error + success), reservations list, instantout, listinstantouts, static withdraw/listwithdrawals/listswaps, listswaps, swapinfo, loop out (forced), abandon swap (help path), plus existing static-loop-in/basic-swaps.
|
||||||
|
- Some paths are intentionally skipped for now:
|
||||||
|
- `stop` (would shut down loopd; replay expects a real gRPC conn).
|
||||||
|
- Asset quote paths (`getAssetAmt`, `unmarshalFixedPoint`) require tapd/asset quotes.
|
||||||
|
- Real dialer/macaroon path handling (`extractPathArgs`, `readMacaroon`, `getClientConn`) aren’t exercised by replay.
|
||||||
|
|
||||||
|
|
||||||
|
## Replay stability notes
|
||||||
|
- Session replay now clones the CLI command tree per run, so flag state (`IsSet`) does not leak between sessions.
|
||||||
|
- Historical warning: earlier replays could have sticky flags across runs; if you see odd ordering-dependent failures, re-check that the replay uses the cloned command path.
|
||||||
|
|
||||||
|
## Bless mode for CLI text changes
|
||||||
|
- Use `LOOP_UPDATE_RECORDED_SESSIONS=true` to let `TestRecordedSessions` rewrite recorded `stdout`, `stderr`, and `run_error` values after a successful offline replay.
|
||||||
|
- Always run bless mode with `-count=1` so the Go test cache does not skip the update:
|
||||||
|
- `LOOP_UPDATE_RECORDED_SESSIONS=true go test ./cmd/loop -run TestRecordedSessions -count=1 -v`
|
||||||
|
- You can target a narrower subset with `-run`, for example:
|
||||||
|
- `LOOP_UPDATE_RECORDED_SESSIONS=true go test ./cmd/loop -run 'TestRecordedSessions/static-openchannel' -count=1 -v`
|
||||||
|
- Bless mode is intentionally narrow:
|
||||||
|
- it reuses the recorded gRPC stream, stdin, and env,
|
||||||
|
- it refuses to bless a session if the command changes from success to failure or vice versa,
|
||||||
|
- and it refuses to bless a session if replay no longer consumes the same recorded gRPC interaction.
|
||||||
|
- Use live recording, not bless mode, when a command’s behavior or RPC flow changed.
|
||||||
|
|
||||||
|
## Session coverage map
|
||||||
|
| Subdir | Commands / scenarios |
|
||||||
|
| --- | --- |
|
||||||
|
| `basic-swaps/` | `loop out` (success), `loop in` (success), `loop monitor` |
|
||||||
|
| `getinfo/` | `loop getinfo` |
|
||||||
|
| `instantout/` | `loop reservations list`, `loop instantout` (ALL + confirm), `loop instantout` (channel flag), `loop instantout` (select index), `loop instantout` (no confirmed reservations), `loop instantout` (cancel), `loop instantout` (invalid selection), `loop listinstantouts` |
|
||||||
|
| `l402/` | `loop listauth`, `loop fetchl402` |
|
||||||
|
| `liquidity/` | `loop getparams`, `loop setparams` (no flags, feepercent, conflict, many flags/categories, destaddr, account, includeallpeers; includes error cases), `loop setrule` (missing threshold, no args, incoming/outgoing/type in/out, clear), `loop suggestswaps` (error + success) |
|
||||||
|
| `loopin/` | `loop in` (invalid amount), `loop in` (external + conf_target error), `loop in` (route_hints + private error), `loop in` (external cancel + verbose, last_hop/amt flag), `loop in` (external force) |
|
||||||
|
| `loopout/` | `loop out` (forced success), `loop out` (invalid amount), `loop out` (addr + account error), `loop out` (invalid account address type), `loop out` (amt flag + channel + max routing fee + payment timeout), `loop out` (addr flag), `loop out` (positional addr), `loop out` (account + account_addr_type) |
|
||||||
|
| `misc/` | `loop terms` |
|
||||||
|
| `quote/` | `loop quote out` (success + verbose), `loop quote in` (help + verbose), `loop quote out` (help), `loop quote in` (deposit_outpoint success), `loop quote in` (positional + last_hop) |
|
||||||
|
| `static/` | `loop static withdraw` (no selection error), `loop static withdraw` (invalid utxo), `loop static withdraw` (all success), `loop static withdraw` (utxo + dest_addr success), `loop static listwithdrawals`, `loop static listswaps` |
|
||||||
|
| `static-autoloop/` | `loop setparams --loopinsource static-address` (success + no-experimental error), `loop getparams` (static-address loop-in source), `loop suggestswaps` (static loop-in suggestion) |
|
||||||
|
| `static-loop-in/` | `loop static new`, `loop static` (help), `loop static listunspent` (incl alias), `loop static listdeposits`, `loop static summary`, `loop static in` (multiple args/flags cases), `loop static in` (duplicate outpoints), `loop static in` (positional low amount error), `loop static in` (positional + last_hop + payment_timeout), `loop static in` (all cancel), `loop static in` (explicit and automatically selected low-confirmation warnings) |
|
||||||
|
| `static-filters/` | `loop static listdeposits --filter ...` for each state (deposited/withdrawing/withdrawn/looping_in/looped_in/publish_expired_deposit/sweep_htlc_timeout/htlc_timeout_swept/wait_for_expiry_sweep/expired/failed) |
|
||||||
|
| `swaps/` | `loop listswaps` (success + conflicting filters + loop_out_only filters + loop_in_only), `loop swapinfo` (success + invalid id + id flag errors), `loop abandonswap` (help + invalid id + success) |
|
||||||
|
|
||||||
|
## Missing / not covered yet
|
||||||
|
- `loop stop` (would terminate loopd; replay expects a live gRPC server).
|
||||||
|
- Docs generators: `loop man`, `loop markdown` (handled in a separate docs pipeline).
|
||||||
|
- Asset/tapd scenarios (explicitly out of scope for now).
|
||||||
146
cmd/loop/testdata/sessions/basic-swaps/01_loop-out.json
vendored
Normal file
146
cmd/loop/testdata/sessions/basic-swaps/01_loop-out.json
vendored
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"loop",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"500000",
|
||||||
|
"--fast"
|
||||||
|
],
|
||||||
|
"env": {},
|
||||||
|
"version": "0.31.7-beta commit=vbump-lndclient-64-g3b735173216470e023b3b72949dd82af9adbebe1 commit_hash=3b735173216470e023b3b72949dd82af9adbebe1",
|
||||||
|
"duration": 1332472129,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"conf_target": 9,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "1769407086",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 22,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.OutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "3875",
|
||||||
|
"prepay_amt_sat": "1337",
|
||||||
|
"htlc_sweep_fee_sat": "3793",
|
||||||
|
"swap_payment_dest": "AhbcGtFvFiW2ru6gxtm66Xv9DpxBdSBXK4a7EX7jbSWd",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 9,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 23,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Send off-chain: 500000 sat\n",
|
||||||
|
"Receive on-chain: 492332 sat\n",
|
||||||
|
"Estimated total fee: 7668 sat\n",
|
||||||
|
"\n",
|
||||||
|
"Fast swap requested.\n",
|
||||||
|
"\n",
|
||||||
|
"CONTINUE SWAP? (y/n): "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1240,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "y\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1241,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.LoopOutRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"dest": "",
|
||||||
|
"max_swap_routing_fee": "10010",
|
||||||
|
"max_prepay_routing_fee": "36",
|
||||||
|
"max_swap_fee": "3875",
|
||||||
|
"max_prepay_amt": "1337",
|
||||||
|
"max_miner_fee": "948250",
|
||||||
|
"loop_out_channel": "0",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"sweep_conf_target": 9,
|
||||||
|
"htlc_confirmations": 1,
|
||||||
|
"swap_publication_deadline": "1769407086",
|
||||||
|
"label": "",
|
||||||
|
"initiator": "loop-cli",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"is_external_addr": false,
|
||||||
|
"reservation_ids": [],
|
||||||
|
"payment_timeout": 0,
|
||||||
|
"asset_info": null,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1327,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SwapResponse",
|
||||||
|
"payload": {
|
||||||
|
"id": "e5f3a1d5cee33e239571d0c1e8cbc6926cbc1d784b9fe185ff6589e949db4549",
|
||||||
|
"id_bytes": "5fOh1c7jPiOVcdDB6MvGkmy8HXhLn+GF/2WJ6UnbRUk=",
|
||||||
|
"htlc_address": "bcrt1p69ekm32epfcy8ymkqdj5clvh37vmlgw76k9x5nfs76c7qgghnpqqmtf80x",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1p69ekm32epfcy8ymkqdj5clvh37vmlgw76k9x5nfs76c7qgghnpqqmtf80x",
|
||||||
|
"server_message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1329,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Swap initiated\n",
|
||||||
|
"ID: e5f3a1d5cee33e239571d0c1e8cbc6926cbc1d784b9fe185ff6589e949db4549\n",
|
||||||
|
"\n",
|
||||||
|
"Run `loop monitor` to monitor progress.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1332,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
131
cmd/loop/testdata/sessions/basic-swaps/02_loop-in.json
vendored
Normal file
131
cmd/loop/testdata/sessions/basic-swaps/02_loop-in.json
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"loop",
|
||||||
|
"in",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"500000"
|
||||||
|
],
|
||||||
|
"env": {},
|
||||||
|
"version": "0.31.7-beta commit=vbump-lndclient-69-g057ef82340ab279a5659ab6ae7edab61448a2acd commit_hash=057ef82340ab279a5659ab6ae7edab61448a2acd",
|
||||||
|
"duration": 2146386627,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 5,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"conf_target": 0,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "0",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 444,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "1825",
|
||||||
|
"htlc_publish_fee_sat": "3875",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 6,
|
||||||
|
"quoted_amt": "500000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 445,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Send on-chain: 500000 sat\n",
|
||||||
|
"Receive off-chain: 494300 sat\n",
|
||||||
|
"Estimated total fee: 5700 sat\n",
|
||||||
|
"\n",
|
||||||
|
"CONTINUE SWAP? (y/n): "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1682,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "y\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1682,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopIn",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.LoopInRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"max_swap_fee": "1825",
|
||||||
|
"max_miner_fee": "11625",
|
||||||
|
"last_hop": "",
|
||||||
|
"external_htlc": false,
|
||||||
|
"htlc_conf_target": 0,
|
||||||
|
"label": "",
|
||||||
|
"initiator": "loop-cli",
|
||||||
|
"route_hints": [],
|
||||||
|
"private": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 2140,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopIn",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SwapResponse",
|
||||||
|
"payload": {
|
||||||
|
"id": "33b431ce62ab7f8f3ff5e2aee9452ec9aa18713e734ecd79fc9cfb4bd77cc7d8",
|
||||||
|
"id_bytes": "M7QxzmKrf48/9eKu6UUuyaoYcT5zTs15/Jz7S9d8x9g=",
|
||||||
|
"htlc_address": "bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu",
|
||||||
|
"server_message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 2141,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Swap initiated\n",
|
||||||
|
"ID: 33b431ce62ab7f8f3ff5e2aee9452ec9aa18713e734ecd79fc9cfb4bd77cc7d8\n",
|
||||||
|
"HTLC address (P2TR): bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu\n",
|
||||||
|
"\n",
|
||||||
|
"Run `loop monitor` to monitor progress.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 2146,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
428
cmd/loop/testdata/sessions/basic-swaps/03_loop-monitor.json
vendored
Normal file
428
cmd/loop/testdata/sessions/basic-swaps/03_loop-monitor.json
vendored
Normal file
|
|
@ -0,0 +1,428 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"loop",
|
||||||
|
"monitor",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {},
|
||||||
|
"version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6",
|
||||||
|
"run_error": "recv: rpc error: code = Canceled desc = context canceled",
|
||||||
|
"duration": 58532880576,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 8,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "send",
|
||||||
|
"message_type": "looprpc.MonitorRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 8,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Note: offchain cost may report as 0 after loopd restart during swap\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 13,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "33b431ce62ab7f8f3ff5e2aee9452ec9aa18713e734ecd79fc9cfb4bd77cc7d8",
|
||||||
|
"id_bytes": "M7QxzmKrf48/9eKu6UUuyaoYcT5zTs15/Jz7S9d8x9g=",
|
||||||
|
"type": "LOOP_IN",
|
||||||
|
"state": "INVOICE_SETTLED",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769413263544127799",
|
||||||
|
"last_update_time": "1769413312288899857",
|
||||||
|
"htlc_address": "bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu",
|
||||||
|
"cost_server": "0",
|
||||||
|
"cost_onchain": "3850",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 13,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "4a8f1dd97addf8222c7613f6a41e3942f4bf9ca64026107c484c1f7e551df658",
|
||||||
|
"id_bytes": "So8d2Xrd+CIsdhP2pB45QvS/nKZAJhB8SEwfflUd9lg=",
|
||||||
|
"type": "LOOP_OUT",
|
||||||
|
"state": "SUCCESS",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769407041704702192",
|
||||||
|
"last_update_time": "1769413318432807159",
|
||||||
|
"htlc_address": "bcrt1pmvvj28wnuwe70xfy6kjzwxcg7cx0kc2dy8l6armutegs8y3p0j7qg0atua",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1pmvvj28wnuwe70xfy6kjzwxcg7cx0kc2dy8l6armutegs8y3p0j7qg0atua",
|
||||||
|
"cost_server": "3875",
|
||||||
|
"cost_onchain": "2775",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 14,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "e5f3a1d5cee33e239571d0c1e8cbc6926cbc1d784b9fe185ff6589e949db4549",
|
||||||
|
"id_bytes": "5fOh1c7jPiOVcdDB6MvGkmy8HXhLn+GF/2WJ6UnbRUk=",
|
||||||
|
"type": "LOOP_OUT",
|
||||||
|
"state": "SUCCESS",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769407087874686997",
|
||||||
|
"last_update_time": "1769413322775449292",
|
||||||
|
"htlc_address": "bcrt1p69ekm32epfcy8ymkqdj5clvh37vmlgw76k9x5nfs76c7qgghnpqqmtf80x",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1p69ekm32epfcy8ymkqdj5clvh37vmlgw76k9x5nfs76c7qgghnpqqmtf80x",
|
||||||
|
"cost_server": "3875",
|
||||||
|
"cost_onchain": "2775",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 14,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "39424d7e6cf6d9c613c4026f5e6da48fd2b65989007895a0db7b937b7f8563e4",
|
||||||
|
"id_bytes": "OUJNfmz22cYTxAJvXm2kj9K2WYkAeJWg23uTe3+FY+Q=",
|
||||||
|
"type": "LOOP_OUT",
|
||||||
|
"state": "SUCCESS",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769413204269210604",
|
||||||
|
"last_update_time": "1769413329442159169",
|
||||||
|
"htlc_address": "bcrt1prz80rm04fhf5y73ps9txxslnhj8e3nqcsnmz6ca6vs6jkrce2l9svtd8sl",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1prz80rm04fhf5y73ps9txxslnhj8e3nqcsnmz6ca6vs6jkrce2l9svtd8sl",
|
||||||
|
"cost_server": "3875",
|
||||||
|
"cost_onchain": "2774",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 14,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-01-26T02:41:52-05:00 LOOP_IN INVOICE_SETTLED 0.00500000 BTC - P2TR: bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu (cost: server 0, onchain 3850, offchain 0)\n",
|
||||||
|
"2026-01-26T02:41:58-05:00 LOOP_OUT SUCCESS 0.00500000 BTC - (cost: server 3875, onchain 2775, offchain 0)\n",
|
||||||
|
"2026-01-26T02:42:02-05:00 LOOP_OUT SUCCESS 0.00500000 BTC - (cost: server 3875, onchain 2775, offchain 0)\n",
|
||||||
|
"2026-01-26T02:42:09-05:00 LOOP_OUT SUCCESS 0.00500000 BTC - (cost: server 3875, onchain 2774, offchain 0)\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 19978,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "13da0be986d8a1f095794f78c5e716150e93c92834e3be52f6eb2fbfc84b8eb0",
|
||||||
|
"id_bytes": "E9oL6YbYofCVeU94xecWFQ6TySg0475S9usvv8hLjrA=",
|
||||||
|
"type": "LOOP_OUT",
|
||||||
|
"state": "INITIATED",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769414430311530071",
|
||||||
|
"last_update_time": "1769414430311530071",
|
||||||
|
"htlc_address": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2",
|
||||||
|
"cost_server": "0",
|
||||||
|
"cost_onchain": "0",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 19978,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-01-26T03:00:30-05:00 LOOP_OUT INITIATED 0.00500000 BTC - \n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 32802,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "3f4ec343ba4087eec3cdf501b4e19733d9685cc151db360bb67d2c924677ce71",
|
||||||
|
"id_bytes": "P07DQ7pAh+7DzfUBtOGXM9loXMFR2zYLtn0skkZ3znE=",
|
||||||
|
"type": "LOOP_IN",
|
||||||
|
"state": "INITIATED",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769414443188873986",
|
||||||
|
"last_update_time": "1769414443188873986",
|
||||||
|
"htlc_address": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch",
|
||||||
|
"cost_server": "0",
|
||||||
|
"cost_onchain": "0",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 32802,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-01-26T03:00:43-05:00 LOOP_IN INITIATED 0.00500000 BTC - P2TR: bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 32829,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "3f4ec343ba4087eec3cdf501b4e19733d9685cc151db360bb67d2c924677ce71",
|
||||||
|
"id_bytes": "P07DQ7pAh+7DzfUBtOGXM9loXMFR2zYLtn0skkZ3znE=",
|
||||||
|
"type": "LOOP_IN",
|
||||||
|
"state": "HTLC_PUBLISHED",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769414443188873986",
|
||||||
|
"last_update_time": "1769414443212089572",
|
||||||
|
"htlc_address": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch",
|
||||||
|
"cost_server": "0",
|
||||||
|
"cost_onchain": "0",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 32829,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-01-26T03:00:43-05:00 LOOP_IN HTLC_PUBLISHED 0.00500000 BTC - P2TR: bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 39513,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "3f4ec343ba4087eec3cdf501b4e19733d9685cc151db360bb67d2c924677ce71",
|
||||||
|
"id_bytes": "P07DQ7pAh+7DzfUBtOGXM9loXMFR2zYLtn0skkZ3znE=",
|
||||||
|
"type": "LOOP_IN",
|
||||||
|
"state": "INVOICE_SETTLED",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769414443188873986",
|
||||||
|
"last_update_time": "1769414449902523194",
|
||||||
|
"htlc_address": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch",
|
||||||
|
"cost_server": "0",
|
||||||
|
"cost_onchain": "3850",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 39513,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-01-26T03:00:49-05:00 LOOP_IN INVOICE_SETTLED 0.00500000 BTC - P2TR: bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch (cost: server 0, onchain 3850, offchain 0)\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 40341,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "13da0be986d8a1f095794f78c5e716150e93c92834e3be52f6eb2fbfc84b8eb0",
|
||||||
|
"id_bytes": "E9oL6YbYofCVeU94xecWFQ6TySg0475S9usvv8hLjrA=",
|
||||||
|
"type": "LOOP_OUT",
|
||||||
|
"state": "PREIMAGE_REVEALED",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769414430311530071",
|
||||||
|
"last_update_time": "1769414450735780318",
|
||||||
|
"htlc_address": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2",
|
||||||
|
"cost_server": "0",
|
||||||
|
"cost_onchain": "0",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 40341,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-01-26T03:00:50-05:00 LOOP_OUT PREIMAGE_REVEALED 0.00500000 BTC - \n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 44110,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "13da0be986d8a1f095794f78c5e716150e93c92834e3be52f6eb2fbfc84b8eb0",
|
||||||
|
"id_bytes": "E9oL6YbYofCVeU94xecWFQ6TySg0475S9usvv8hLjrA=",
|
||||||
|
"type": "LOOP_OUT",
|
||||||
|
"state": "SUCCESS",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1769414430311530071",
|
||||||
|
"last_update_time": "1769414454503356191",
|
||||||
|
"htlc_address": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2",
|
||||||
|
"cost_server": "3873",
|
||||||
|
"cost_onchain": "2774",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 44110,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-01-26T03:00:54-05:00 LOOP_OUT SUCCESS 0.00500000 BTC - (cost: server 3873, onchain 2774, offchain 0)\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 58531,
|
||||||
|
"kind": "signal",
|
||||||
|
"data": {
|
||||||
|
"signal": "interrupt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 58531,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "error",
|
||||||
|
"error": "rpc error: code = Canceled desc = context canceled",
|
||||||
|
"status": {
|
||||||
|
"code": 1,
|
||||||
|
"message": "context canceled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 58532,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] recv: rpc error: code = Canceled desc = context canceled\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 58532,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "recv: rpc error: code = Canceled desc = context canceled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
267
cmd/loop/testdata/sessions/basic-swaps/04_loop-monitor-static-loop-in.json
vendored
Normal file
267
cmd/loop/testdata/sessions/basic-swaps/04_loop-monitor-static-loop-in.json
vendored
Normal file
|
|
@ -0,0 +1,267 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"loop",
|
||||||
|
"monitor",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {},
|
||||||
|
"version": "0.33.3-beta commit= commit_hash=",
|
||||||
|
"run_error": "recv: rpc error: code = Canceled desc = context canceled",
|
||||||
|
"duration": 25407490958,
|
||||||
|
"clock_start_unix": 1784150305
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 13,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "send",
|
||||||
|
"message_type": "looprpc.MonitorRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 13,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Note: offchain cost may report as 0 after loopd restart during swap\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 16479,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||||
|
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||||
|
"type": "STATIC_LOOP_IN",
|
||||||
|
"state": "INITIATED",
|
||||||
|
"static_loop_in_state": "INIT_HTLC",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1784150320647961000",
|
||||||
|
"last_update_time": "1784150321494895000",
|
||||||
|
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2tr": "",
|
||||||
|
"cost_server": "0",
|
||||||
|
"cost_onchain": "0",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 16479,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-07-15T18:18:41-03:00 STATIC_LOOP_IN INIT_HTLC 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 16482,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||||
|
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||||
|
"type": "STATIC_LOOP_IN",
|
||||||
|
"state": "INITIATED",
|
||||||
|
"static_loop_in_state": "SIGN_HTLC_TX",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1784150320647961000",
|
||||||
|
"last_update_time": "1784150321499639000",
|
||||||
|
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2tr": "",
|
||||||
|
"cost_server": "0",
|
||||||
|
"cost_onchain": "0",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 16482,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-07-15T18:18:41-03:00 STATIC_LOOP_IN SIGN_HTLC_TX 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 16530,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||||
|
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||||
|
"type": "STATIC_LOOP_IN",
|
||||||
|
"state": "INITIATED",
|
||||||
|
"static_loop_in_state": "MONITOR_INVOICE_HTLC_TX",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1784150320647961000",
|
||||||
|
"last_update_time": "1784150321548450000",
|
||||||
|
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2tr": "",
|
||||||
|
"cost_server": "0",
|
||||||
|
"cost_onchain": "0",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 16530,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-07-15T18:18:41-03:00 STATIC_LOOP_IN MONITOR_INVOICE_HTLC_TX 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 17027,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||||
|
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||||
|
"type": "STATIC_LOOP_IN",
|
||||||
|
"state": "INITIATED",
|
||||||
|
"static_loop_in_state": "PAYMENT_RECEIVED",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1784150320647961000",
|
||||||
|
"last_update_time": "1784150322044521000",
|
||||||
|
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2tr": "",
|
||||||
|
"cost_server": "1828",
|
||||||
|
"cost_onchain": "0",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 17027,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-07-15T18:18:42-03:00 STATIC_LOOP_IN PAYMENT_RECEIVED 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv (cost: server 1828, onchain 0, offchain 0)\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 17036,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "recv",
|
||||||
|
"message_type": "looprpc.SwapStatus",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"id": "bec3777d45337f652d96136e5714ed90a5d4f7cda87b5842110ecc38c9a65826",
|
||||||
|
"id_bytes": "vsN3fUUzf2UtlhNuVxTtkKXU982oe1hCEQ7MOMmmWCY=",
|
||||||
|
"type": "STATIC_LOOP_IN",
|
||||||
|
"state": "INITIATED",
|
||||||
|
"static_loop_in_state": "SUCCEEDED",
|
||||||
|
"failure_reason": "FAILURE_REASON_NONE",
|
||||||
|
"initiation_time": "1784150320647961000",
|
||||||
|
"last_update_time": "1784150322052572000",
|
||||||
|
"htlc_address": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2wsh": "bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv",
|
||||||
|
"htlc_address_p2tr": "",
|
||||||
|
"cost_server": "1828",
|
||||||
|
"cost_onchain": "0",
|
||||||
|
"cost_offchain": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"label": "",
|
||||||
|
"asset_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 17036,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"2026-07-15T18:18:42-03:00 STATIC_LOOP_IN SUCCEEDED 0.00500000 BTC - P2WSH: bcrt1qjsl6tmv2vf3p5wwv28jh6xwettds934k02c3l5w6zyjsyrsd3lfskgdjmv (cost: server 1828, onchain 0, offchain 0)\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 25406,
|
||||||
|
"kind": "signal",
|
||||||
|
"data": {
|
||||||
|
"signal": "interrupt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 25407,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/Monitor",
|
||||||
|
"event": "error",
|
||||||
|
"error": "rpc error: code = Canceled desc = context canceled",
|
||||||
|
"status": {
|
||||||
|
"code": 1,
|
||||||
|
"message": "context canceled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 25407,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] recv: rpc error: code = Canceled desc = context canceled\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 25407,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "recv: rpc error: code = Canceled desc = context canceled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
96
cmd/loop/testdata/sessions/getinfo/01_loop-getinfo.json
vendored
Normal file
96
cmd/loop/testdata/sessions/getinfo/01_loop-getinfo.json
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"getinfo",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 200590737,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 4,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetInfo",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetInfoRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 200,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetInfo",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.GetInfoResponse",
|
||||||
|
"payload": {
|
||||||
|
"version": "0.31.7-beta",
|
||||||
|
"network": "regtest",
|
||||||
|
"rpc_listen": "localhost:11010",
|
||||||
|
"rest_listen": "localhost:8081",
|
||||||
|
"macaroon_path": "/home/user/.loop/regtest/loop.macaroon",
|
||||||
|
"tls_cert_path": "/home/user/.loop/regtest/tls.cert",
|
||||||
|
"loop_out_stats": {
|
||||||
|
"pending_count": "0",
|
||||||
|
"success_count": "0",
|
||||||
|
"fail_count": "0",
|
||||||
|
"sum_pending_amt": "0",
|
||||||
|
"sum_succeeded_amt": "0"
|
||||||
|
},
|
||||||
|
"loop_in_stats": {
|
||||||
|
"pending_count": "0",
|
||||||
|
"success_count": "0",
|
||||||
|
"fail_count": "0",
|
||||||
|
"sum_pending_amt": "0",
|
||||||
|
"sum_succeeded_amt": "0"
|
||||||
|
},
|
||||||
|
"commit_hash": "579ea31979e0cdb1ee6049ab97cc2768888eac40"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 200,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"commit_hash\": \"579ea31979e0cdb1ee6049ab97cc2768888eac40\",\n",
|
||||||
|
" \"loop_in_stats\": {\n",
|
||||||
|
" \"fail_count\": \"0\",\n",
|
||||||
|
" \"pending_count\": \"0\",\n",
|
||||||
|
" \"success_count\": \"0\",\n",
|
||||||
|
" \"sum_pending_amt\": \"0\",\n",
|
||||||
|
" \"sum_succeeded_amt\": \"0\"\n",
|
||||||
|
" },\n",
|
||||||
|
" \"loop_out_stats\": {\n",
|
||||||
|
" \"fail_count\": \"0\",\n",
|
||||||
|
" \"pending_count\": \"0\",\n",
|
||||||
|
" \"success_count\": \"0\",\n",
|
||||||
|
" \"sum_pending_amt\": \"0\",\n",
|
||||||
|
" \"sum_succeeded_amt\": \"0\"\n",
|
||||||
|
" },\n",
|
||||||
|
" \"macaroon_path\": \"/home/user/.loop/regtest/loop.macaroon\",\n",
|
||||||
|
" \"network\": \"regtest\",\n",
|
||||||
|
" \"rest_listen\": \"localhost:8081\",\n",
|
||||||
|
" \"rpc_listen\": \"localhost:11010\",\n",
|
||||||
|
" \"tls_cert_path\": \"/home/user/.loop/regtest/tls.cert\",\n",
|
||||||
|
" \"version\": \"0.31.7-beta\"\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 200,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
107
cmd/loop/testdata/sessions/instantout/01_loop-reservations-list.json
vendored
Normal file
107
cmd/loop/testdata/sessions/instantout/01_loop-reservations-list.json
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"reservations",
|
||||||
|
"list",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 177945855,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListReservationsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 177,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListReservationsResponse",
|
||||||
|
"payload": {
|
||||||
|
"reservations": [
|
||||||
|
{
|
||||||
|
"reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1619
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1623
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1626
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 177,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"reservations\": [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"amount\": \"800000\",\n",
|
||||||
|
" \"expiry\": 1619,\n",
|
||||||
|
" \"reservation_id\": \"b3f443e8b8ff797766b450ad77fe80beb250acfd7887562b6e5c9964c5884d87\",\n",
|
||||||
|
" \"state\": \"Confirmed\",\n",
|
||||||
|
" \"tx_id\": \"8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17\",\n",
|
||||||
|
" \"vout\": 0\n",
|
||||||
|
" },\n",
|
||||||
|
" {\n",
|
||||||
|
" \"amount\": \"800000\",\n",
|
||||||
|
" \"expiry\": 1623,\n",
|
||||||
|
" \"reservation_id\": \"5a5f824ec63e576cd4be42b96ecf5622db64bca2eb28cf00d64a59e5567f307b\",\n",
|
||||||
|
" \"state\": \"Confirmed\",\n",
|
||||||
|
" \"tx_id\": \"beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836\",\n",
|
||||||
|
" \"vout\": 0\n",
|
||||||
|
" },\n",
|
||||||
|
" {\n",
|
||||||
|
" \"amount\": \"800000\",\n",
|
||||||
|
" \"expiry\": 1626,\n",
|
||||||
|
" \"reservation_id\": \"32eeb97db85ac84b51ce8ba028b067a1e44df1ffad10cd7e3bd42e36f5087db2\",\n",
|
||||||
|
" \"state\": \"Confirmed\",\n",
|
||||||
|
" \"tx_id\": \"5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952\",\n",
|
||||||
|
" \"vout\": 0\n",
|
||||||
|
" }\n",
|
||||||
|
" ]\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 177,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
190
cmd/loop/testdata/sessions/instantout/02_loop-instantout.json
vendored
Normal file
190
cmd/loop/testdata/sessions/instantout/02_loop-instantout.json
vendored
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"instantout",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 1574538838,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 0,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "ALL\ny\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListReservationsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 152,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListReservationsResponse",
|
||||||
|
"payload": {
|
||||||
|
"reservations": [
|
||||||
|
{
|
||||||
|
"reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1619
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1623
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1626
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 152,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Available reservations: \n",
|
||||||
|
"\n",
|
||||||
|
"Reservation 1: shortid b3f443, amt 800000, expiry height 1619 \n",
|
||||||
|
"Reservation 2: shortid 5a5f82, amt 800000, expiry height 1623 \n",
|
||||||
|
"Reservation 3: shortid 32eeb9, amt 800000, expiry height 1626 \n",
|
||||||
|
"\n",
|
||||||
|
"Max amount to instant out: 2400000\n",
|
||||||
|
"\n",
|
||||||
|
"Select reservations for instantout (e.g. '1,2,3')\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 152,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Type 'ALL' to use all available reservations.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 152,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.InstantOutQuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "2400000",
|
||||||
|
"num_reservations": 0,
|
||||||
|
"reservation_ids": [
|
||||||
|
"s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI="
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 177,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InstantOutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"service_fee_sat": "4800",
|
||||||
|
"sweep_fee_sat": "5650"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 177,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOut",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.InstantOutRequest",
|
||||||
|
"payload": {
|
||||||
|
"reservation_ids": [
|
||||||
|
"s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI="
|
||||||
|
],
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"dest_addr": "",
|
||||||
|
"max_swap_fee_sat": "4800"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 177,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"\n",
|
||||||
|
"Estimated on-chain fee: 5650 sat\n",
|
||||||
|
"Service fee: 4800 sat\n",
|
||||||
|
"\n",
|
||||||
|
"CONTINUE SWAP? (y/n): Starting instant swap out\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1574,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOut",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InstantOutResponse",
|
||||||
|
"payload": {
|
||||||
|
"instant_out_hash": "uxbiybKoHjn4B4b+yHRFGI9e12jMMtTLEvNnF7wYriU=",
|
||||||
|
"sweep_tx_id": "a1650cb339118e58f317bbbbd8cff720e0cc02a9fe159c9286724d6f5722fef2",
|
||||||
|
"state": "PushPreimage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1574,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Instant out swap initiated with ID: bb16e2c9b2a81e39f80786fec87445188f5ed768cc32d4cb12f36717bc18ae25, State: PushPreimage \n",
|
||||||
|
"Sweepless sweep tx id: a1650cb339118e58f317bbbbd8cff720e0cc02a9fe159c9286724d6f5722fef2 \n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1574,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
80
cmd/loop/testdata/sessions/instantout/03_loop-listinstantouts.json
vendored
Normal file
80
cmd/loop/testdata/sessions/instantout/03_loop-listinstantouts.json
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"listinstantouts",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 142800927,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListInstantOuts",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListInstantOutsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 142,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListInstantOuts",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListInstantOutsResponse",
|
||||||
|
"payload": {
|
||||||
|
"swaps": [
|
||||||
|
{
|
||||||
|
"swap_hash": "uxbiybKoHjn4B4b+yHRFGI9e12jMMtTLEvNnF7wYriU=",
|
||||||
|
"state": "WaitForSweeplessSweepConfirmed",
|
||||||
|
"amount": "2400000",
|
||||||
|
"reservation_ids": [
|
||||||
|
"s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI="
|
||||||
|
],
|
||||||
|
"sweep_tx_id": "a1650cb339118e58f317bbbbd8cff720e0cc02a9fe159c9286724d6f5722fef2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 142,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"swaps\": [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"amount\": \"2400000\",\n",
|
||||||
|
" \"reservation_ids\": [\n",
|
||||||
|
" \"b3f443e8b8ff797766b450ad77fe80beb250acfd7887562b6e5c9964c5884d87\",\n",
|
||||||
|
" \"5a5f824ec63e576cd4be42b96ecf5622db64bca2eb28cf00d64a59e5567f307b\",\n",
|
||||||
|
" \"32eeb97db85ac84b51ce8ba028b067a1e44df1ffad10cd7e3bd42e36f5087db2\"\n",
|
||||||
|
" ],\n",
|
||||||
|
" \"state\": \"WaitForSweeplessSweepConfirmed\",\n",
|
||||||
|
" \"swap_hash\": \"bb16e2c9b2a81e39f80786fec87445188f5ed768cc32d4cb12f36717bc18ae25\",\n",
|
||||||
|
" \"sweep_tx_id\": \"a1650cb339118e58f317bbbbd8cff720e0cc02a9fe159c9286724d6f5722fef2\"\n",
|
||||||
|
" }\n",
|
||||||
|
" ]\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 142,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
94
cmd/loop/testdata/sessions/instantout/04_loop-instantout-no-confirmed.json
vendored
Normal file
94
cmd/loop/testdata/sessions/instantout/04_loop-instantout-no-confirmed.json
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"instantout",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 271911644,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "ALL\nn\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 9,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListReservationsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 270,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListReservationsResponse",
|
||||||
|
"payload": {
|
||||||
|
"reservations": [
|
||||||
|
{
|
||||||
|
"reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1619
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1623
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1626
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=",
|
||||||
|
"state": "WaitForConfirmation",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1632
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 271,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"No confirmed reservations found \n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 271,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
155
cmd/loop/testdata/sessions/instantout/05_loop-instantout-cancel.json
vendored
Normal file
155
cmd/loop/testdata/sessions/instantout/05_loop-instantout-cancel.json
vendored
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"instantout",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "swap canceled",
|
||||||
|
"duration": 229272887,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "ALL\nn\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListReservationsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 188,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListReservationsResponse",
|
||||||
|
"payload": {
|
||||||
|
"reservations": [
|
||||||
|
{
|
||||||
|
"reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1619
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1623
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1626
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "322d031e9ab55e0bcdac421c57d2d5d7705b80d01d3217a672aa1bf73edfc963",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1632
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 188,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.InstantOutQuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "800000",
|
||||||
|
"num_reservations": 0,
|
||||||
|
"reservation_ids": [
|
||||||
|
"/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U="
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 188,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Available reservations: \n",
|
||||||
|
"\n",
|
||||||
|
"Reservation 1: shortid fc0b25, amt 800000, expiry height 1632 \n",
|
||||||
|
"\n",
|
||||||
|
"Max amount to instant out: 800000\n",
|
||||||
|
"\n",
|
||||||
|
"Select reservations for instantout (e.g. '1,2,3')\n",
|
||||||
|
"Type 'ALL' to use all available reservations.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 228,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InstantOutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"service_fee_sat": "1600",
|
||||||
|
"sweep_fee_sat": "2773"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 228,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"\n",
|
||||||
|
"Estimated on-chain fee: 2773 sat\n",
|
||||||
|
"Service fee: 1600 sat\n",
|
||||||
|
"\n",
|
||||||
|
"CONTINUE SWAP? (y/n): "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 229,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] swap canceled\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 229,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "swap canceled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
113
cmd/loop/testdata/sessions/instantout/06_loop-instantout-invalid-selection.json
vendored
Normal file
113
cmd/loop/testdata/sessions/instantout/06_loop-instantout-invalid-selection.json
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"instantout",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "invalid index 99",
|
||||||
|
"duration": 203873152,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 0,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "99\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListReservationsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 202,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListReservationsResponse",
|
||||||
|
"payload": {
|
||||||
|
"reservations": [
|
||||||
|
{
|
||||||
|
"reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1619
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1623
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1626
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "322d031e9ab55e0bcdac421c57d2d5d7705b80d01d3217a672aa1bf73edfc963",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1632
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 202,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Available reservations: \n",
|
||||||
|
"\n",
|
||||||
|
"Reservation 1: shortid fc0b25, amt 800000, expiry height 1632 \n",
|
||||||
|
"\n",
|
||||||
|
"Max amount to instant out: 800000\n",
|
||||||
|
"\n",
|
||||||
|
"Select reservations for instantout (e.g. '1,2,3')\n",
|
||||||
|
"Type 'ALL' to use all available reservations.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 203,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] invalid index 99\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 203,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "invalid index 99"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
207
cmd/loop/testdata/sessions/instantout/07_loop-instantout-channel.json
vendored
Normal file
207
cmd/loop/testdata/sessions/instantout/07_loop-instantout-channel.json
vendored
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"instantout",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--channel",
|
||||||
|
"125344325763072"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 1716733605,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "ALL\ny\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListReservationsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 317,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListReservationsResponse",
|
||||||
|
"payload": {
|
||||||
|
"reservations": [
|
||||||
|
{
|
||||||
|
"reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1619
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1623
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1626
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "322d031e9ab55e0bcdac421c57d2d5d7705b80d01d3217a672aa1bf73edfc963",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1632
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "0JK1m4RmC5iKV9dR/SlixC0IEGmobPMTlVgmk13thx8=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "081eed1a78e87498ca29af380aaecc64e949c47aeb400375e662fdf630554ace",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1653
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 317,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.InstantOutQuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "1600000",
|
||||||
|
"num_reservations": 0,
|
||||||
|
"reservation_ids": [
|
||||||
|
"/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=",
|
||||||
|
"0JK1m4RmC5iKV9dR/SlixC0IEGmobPMTlVgmk13thx8="
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 317,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Available reservations: \n",
|
||||||
|
"\n",
|
||||||
|
"Reservation 1: shortid fc0b25, amt 800000, expiry height 1632 \n",
|
||||||
|
"Reservation 2: shortid d092b5, amt 800000, expiry height 1653 \n",
|
||||||
|
"\n",
|
||||||
|
"Max amount to instant out: 1600000\n",
|
||||||
|
"\n",
|
||||||
|
"Select reservations for instantout (e.g. '1,2,3')\n",
|
||||||
|
"Type 'ALL' to use all available reservations.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 359,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InstantOutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"service_fee_sat": "3200",
|
||||||
|
"sweep_fee_sat": "4210"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 359,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOut",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.InstantOutRequest",
|
||||||
|
"payload": {
|
||||||
|
"reservation_ids": [
|
||||||
|
"/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=",
|
||||||
|
"0JK1m4RmC5iKV9dR/SlixC0IEGmobPMTlVgmk13thx8="
|
||||||
|
],
|
||||||
|
"outgoing_chan_set": [
|
||||||
|
"125344325763072"
|
||||||
|
],
|
||||||
|
"dest_addr": "",
|
||||||
|
"max_swap_fee_sat": "3200"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 360,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"\n",
|
||||||
|
"Estimated on-chain fee: 4210 sat\n",
|
||||||
|
"Service fee: 3200 sat\n",
|
||||||
|
"\n",
|
||||||
|
"CONTINUE SWAP? (y/n): Starting instant swap out\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1711,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOut",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InstantOutResponse",
|
||||||
|
"payload": {
|
||||||
|
"instant_out_hash": "RfjHjKyop9NqqQzGXnyF1mnv9mPeWJXyz+5seKnKZ4k=",
|
||||||
|
"sweep_tx_id": "6f6f29961d2b4ab9f893850a0250f1f55fb93b144966833a5546986b171255a4",
|
||||||
|
"state": "PushPreimage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1711,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Instant out swap initiated with ID: 45f8c78caca8a7d36aa90cc65e7c85d669eff663de5895f2cfee6c78a9ca6789, State: PushPreimage \n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1711,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Sweepless sweep tx id: 6f6f29961d2b4ab9f893850a0250f1f55fb93b144966833a5546986b171255a4 \n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1716,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
200
cmd/loop/testdata/sessions/instantout/08_loop-instantout-select-index.json
vendored
Normal file
200
cmd/loop/testdata/sessions/instantout/08_loop-instantout-select-index.json
vendored
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"instantout",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 1377442432,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "1\ny\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListReservationsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 178,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListReservations",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListReservationsResponse",
|
||||||
|
"payload": {
|
||||||
|
"reservations": [
|
||||||
|
{
|
||||||
|
"reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1619
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1623
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1626
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "322d031e9ab55e0bcdac421c57d2d5d7705b80d01d3217a672aa1bf73edfc963",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1632
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "0JK1m4RmC5iKV9dR/SlixC0IEGmobPMTlVgmk13thx8=",
|
||||||
|
"state": "Spent",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "081eed1a78e87498ca29af380aaecc64e949c47aeb400375e662fdf630554ace",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1653
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reservation_id": "cSfKVONNmsK9+p4Uc5nc3ZtE+37uOODHeq1vprhh/x4=",
|
||||||
|
"state": "Confirmed",
|
||||||
|
"amount": "800000",
|
||||||
|
"tx_id": "46808a670542c1f3bf20ade2af77c254e3535378aab6ccd32c58de9976b0cf26",
|
||||||
|
"vout": 0,
|
||||||
|
"expiry": 1671
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 179,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Available reservations: \n",
|
||||||
|
"\n",
|
||||||
|
"Reservation 1: shortid 7127ca, amt 800000, expiry height 1671 \n",
|
||||||
|
"\n",
|
||||||
|
"Max amount to instant out: 800000\n",
|
||||||
|
"\n",
|
||||||
|
"Select reservations for instantout (e.g. '1,2,3')\n",
|
||||||
|
"Type 'ALL' to use all available reservations.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 179,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.InstantOutQuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "800000",
|
||||||
|
"num_reservations": 0,
|
||||||
|
"reservation_ids": [
|
||||||
|
"cSfKVONNmsK9+p4Uc5nc3ZtE+37uOODHeq1vprhh/x4="
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 186,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InstantOutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"service_fee_sat": "1600",
|
||||||
|
"sweep_fee_sat": "2772"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 186,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"\n",
|
||||||
|
"Estimated on-chain fee: 2772 sat\n",
|
||||||
|
"Service fee: 1600 sat\n",
|
||||||
|
"\n",
|
||||||
|
"CONTINUE SWAP? (y/n): Starting instant swap out\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 186,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOut",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.InstantOutRequest",
|
||||||
|
"payload": {
|
||||||
|
"reservation_ids": [
|
||||||
|
"cSfKVONNmsK9+p4Uc5nc3ZtE+37uOODHeq1vprhh/x4="
|
||||||
|
],
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"dest_addr": "",
|
||||||
|
"max_swap_fee_sat": "1600"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1376,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/InstantOut",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InstantOutResponse",
|
||||||
|
"payload": {
|
||||||
|
"instant_out_hash": "0LvpOmXv+cqCnXgQ/lNOlbCvlI0AZkkkS1pFJk2n3us=",
|
||||||
|
"sweep_tx_id": "650e166a7d346f2b268c26c7fe075d8e2dc25655a747f546f5a039f01973d979",
|
||||||
|
"state": "WaitForSweeplessSweepConfirmed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1377,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Instant out swap initiated with ID: d0bbe93a65eff9ca829d7810fe534e95b0af948d006649244b5a45264da7deeb, State: WaitForSweeplessSweepConfirmed \n",
|
||||||
|
"Sweepless sweep tx id: 650e166a7d346f2b268c26c7fe075d8e2dc25655a747f546f5a039f01973d979 \n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1377,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
79
cmd/loop/testdata/sessions/l402/01_loop-listauth.json
vendored
Normal file
79
cmd/loop/testdata/sessions/l402/01_loop-listauth.json
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"listauth",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 157631491,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetL402Tokens",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.TokensRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 157,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetL402Tokens",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.TokensResponse",
|
||||||
|
"payload": {
|
||||||
|
"tokens": [
|
||||||
|
{
|
||||||
|
"base_macaroon": "AgEEbHNhdAJCAAAkJPkWepsd1GgURnFywD7MZi7yoPZC/BjJHNrIs6en83L1T2GVJqpTIlrcw09RnFx5xHfjsNDjLRIekGAPVN/oAAIPc2VydmljZXM9bG9vcDowAAISbG9vcF9jYXBhYmlsaXRpZXM9AAAGIGIkKNbF9/LxQzqEKurnzP7lkGJbA2CsVbkwrKXyUTx/",
|
||||||
|
"payment_hash": "JCT5FnqbHdRoFEZxcsA+zGYu8qD2QvwYyRzayLOnp/M=",
|
||||||
|
"payment_preimage": "pSZZqEORw/4tPCPPS3sTDeh/2mq3EAzwqqWor/xwTmM=",
|
||||||
|
"amount_paid_msat": "1000000",
|
||||||
|
"routing_fee_paid_msat": "0",
|
||||||
|
"time_created": "1770076917",
|
||||||
|
"expired": false,
|
||||||
|
"storage_name": "/home/user/.loop/regtest/l402.token",
|
||||||
|
"id": "72f54f619526aa53225adcc34f519c5c79c477e3b0d0e32d121e90600f54dfe8"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 157,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[\n",
|
||||||
|
" {\n",
|
||||||
|
" \"amount_paid_msat\": 1000000,\n",
|
||||||
|
" \"base_macaroon\": \"0201046c736174024200002424f9167a9b1dd46814467172c03ecc662ef2a0f642fc18c91cdac8b3a7a7f372f54f619526aa53225adcc34f519c5c79c477e3b0d0e32d121e90600f54dfe800020f73657276696365733d6c6f6f703a300002126c6f6f705f6361706162696c69746965733d00000620622428d6c5f7f2f1433a842aeae7ccfee590625b0360ac55b930aca5f2513c7f\",\n",
|
||||||
|
" \"expired\": false,\n",
|
||||||
|
" \"file_name\": \"/home/user/.loop/regtest/l402.token\",\n",
|
||||||
|
" \"id\": \"72f54f619526aa53225adcc34f519c5c79c477e3b0d0e32d121e90600f54dfe8\",\n",
|
||||||
|
" \"payment_hash\": \"2424f9167a9b1dd46814467172c03ecc662ef2a0f642fc18c91cdac8b3a7a7f3\",\n",
|
||||||
|
" \"payment_preimage\": \"a52659a84391c3fe2d3c23cf4b7b130de87fda6ab7100cf0aaa5a8affc704e63\",\n",
|
||||||
|
" \"routing_fee_paid_msat\": 0,\n",
|
||||||
|
" \"time_created\": \"2026-02-02T19:01:57-05:00\",\n",
|
||||||
|
" \"valid_until\": \"\"\n",
|
||||||
|
" }\n",
|
||||||
|
"]\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 157,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
52
cmd/loop/testdata/sessions/l402/02_loop-fetchl402.json
vendored
Normal file
52
cmd/loop/testdata/sessions/l402/02_loop-fetchl402.json
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"fetchl402",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 177710196,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/FetchL402Token",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.FetchL402TokenRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 177,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/FetchL402Token",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.FetchL402TokenResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 177,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 177,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
110
cmd/loop/testdata/sessions/liquidity/01_loop-getparams.json
vendored
Normal file
110
cmd/loop/testdata/sessions/liquidity/01_loop-getparams.json
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"getparams",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 213058408,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 212,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 212,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"account\": \"\",\n",
|
||||||
|
" \"account_addr_type\": \"ADDRESS_TYPE_UNKNOWN\",\n",
|
||||||
|
" \"auto_max_in_flight\": \"1\",\n",
|
||||||
|
" \"autoloop\": false,\n",
|
||||||
|
" \"autoloop_budget_last_refresh\": \"1770076824\",\n",
|
||||||
|
" \"autoloop_budget_refresh_period_sec\": \"604800\",\n",
|
||||||
|
" \"autoloop_budget_sat\": \"335544\",\n",
|
||||||
|
" \"autoloop_budget_start_sec\": \"0\",\n",
|
||||||
|
" \"autoloop_dest_address\": \"\",\n",
|
||||||
|
" \"easy_asset_params\": {},\n",
|
||||||
|
" \"easy_autoloop\": false,\n",
|
||||||
|
" \"easy_autoloop_excluded_peers\": [],\n",
|
||||||
|
" \"easy_autoloop_local_target_sat\": \"0\",\n",
|
||||||
|
" \"failure_backoff_sec\": \"86400\",\n",
|
||||||
|
" \"fast_swap_publication\": true,\n",
|
||||||
|
" \"fee_ppm\": \"20000\",\n",
|
||||||
|
" \"htlc_conf_target\": 6,\n",
|
||||||
|
" \"loop_in_source\": \"LOOP_IN_SOURCE_WALLET\",\n",
|
||||||
|
" \"max_miner_fee_sat\": \"0\",\n",
|
||||||
|
" \"max_prepay_routing_fee_ppm\": \"0\",\n",
|
||||||
|
" \"max_prepay_sat\": \"0\",\n",
|
||||||
|
" \"max_routing_fee_ppm\": \"0\",\n",
|
||||||
|
" \"max_swap_amount\": \"0\",\n",
|
||||||
|
" \"max_swap_fee_ppm\": \"0\",\n",
|
||||||
|
" \"min_swap_amount\": \"0\",\n",
|
||||||
|
" \"rules\": [],\n",
|
||||||
|
" \"sweep_conf_target\": 100,\n",
|
||||||
|
" \"sweep_fee_rate_sat_per_vbyte\": \"0\"\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 213,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
93
cmd/loop/testdata/sessions/liquidity/02_loop-setparams-no-flags.json
vendored
Normal file
93
cmd/loop/testdata/sessions/liquidity/02_loop-setparams-no-flags.json
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "at least one flag required to set params",
|
||||||
|
"duration": 150177446,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 5,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 149,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 150,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] at least one flag required to set params\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 150,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "at least one flag required to set params"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
125
cmd/loop/testdata/sessions/liquidity/03_loop-setparams-feepercent.json
vendored
Normal file
125
cmd/loop/testdata/sessions/liquidity/03_loop-setparams-feepercent.json
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--feepercent",
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 249608692,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 6,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 188,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 189,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 248,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 249,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
97
cmd/loop/testdata/sessions/liquidity/04_loop-setparams-feepercent-conflict.json
vendored
Normal file
97
cmd/loop/testdata/sessions/liquidity/04_loop-setparams-feepercent-conflict.json
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--feepercent",
|
||||||
|
"2",
|
||||||
|
"--sweeplimit",
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "feepercent cannot be set with specific fee category flags",
|
||||||
|
"duration": 161206474,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 4,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 159,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 161,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] feepercent cannot be set with specific fee category flags\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 161,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "feepercent cannot be set with specific fee category flags"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
94
cmd/loop/testdata/sessions/liquidity/05_loop-setrule-missing-threshold.json
vendored
Normal file
94
cmd/loop/testdata/sessions/liquidity/05_loop-setrule-missing-threshold.json
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setrule",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"034fa8802fdc9f8c137e8f2cce2e4d30e590a1314e16bd060ac7d772dc698ff4bc"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "provide at least one flag to set rules or use the --clear flag to remove rules",
|
||||||
|
"duration": 143258905,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 142,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 143,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] provide at least one flag to set rules or use the --clear flag to remove rules\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 143,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "provide at least one flag to set rules or use the --clear flag to remove rules"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
35
cmd/loop/testdata/sessions/liquidity/06_loop-setrule-no-args.json
vendored
Normal file
35
cmd/loop/testdata/sessions/liquidity/06_loop-setrule-no-args.json
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setrule",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "please set a channel id or peer pubkey for the rule update",
|
||||||
|
"duration": 1924009,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] please set a channel id or peer pubkey for the rule update\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "please set a channel id or peer pubkey for the rule update"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
58
cmd/loop/testdata/sessions/liquidity/07_loop-suggestswaps.json
vendored
Normal file
58
cmd/loop/testdata/sessions/liquidity/07_loop-suggestswaps.json
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"suggestswaps",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "no rules set for autolooper, please set rules using the setrule command",
|
||||||
|
"duration": 178932401,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SuggestSwaps",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SuggestSwapsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 178,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SuggestSwaps",
|
||||||
|
"event": "error",
|
||||||
|
"error": "rpc error: code = FailedPrecondition desc = no rules set for autoloop",
|
||||||
|
"status": {
|
||||||
|
"code": 9,
|
||||||
|
"message": "no rules set for autoloop"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 178,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] no rules set for autolooper, please set rules using the setrule command\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 178,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "no rules set for autolooper, please set rules using the setrule command"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
144
cmd/loop/testdata/sessions/liquidity/08_loop-setrule-success.json
vendored
Normal file
144
cmd/loop/testdata/sessions/liquidity/08_loop-setrule-success.json
vendored
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setrule",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--incoming_threshold",
|
||||||
|
"10",
|
||||||
|
"034fa8802fdc9f8c137e8f2cce2e4d30e590a1314e16bd060ac7d772dc698ff4bc"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 130661787,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 124,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 124,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 130,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 130,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
61
cmd/loop/testdata/sessions/liquidity/09_loop-suggestswaps-success.json
vendored
Normal file
61
cmd/loop/testdata/sessions/liquidity/09_loop-suggestswaps-success.json
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"suggestswaps",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 137916583,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SuggestSwaps",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SuggestSwapsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 137,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SuggestSwaps",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SuggestSwapsResponse",
|
||||||
|
"payload": {
|
||||||
|
"loop_out": [],
|
||||||
|
"loop_in": [],
|
||||||
|
"disqualified": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 137,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"disqualified\": [],\n",
|
||||||
|
" \"loop_in\": [],\n",
|
||||||
|
" \"loop_out\": [],\n",
|
||||||
|
" \"static_loop_in\": []\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 137,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
152
cmd/loop/testdata/sessions/liquidity/10_loop-setrule-incoming.json
vendored
Normal file
152
cmd/loop/testdata/sessions/liquidity/10_loop-setrule-incoming.json
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setrule",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--incoming_threshold",
|
||||||
|
"40",
|
||||||
|
"125344325763072"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 204743867,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 135,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 135,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_id": "125344325763072",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 40,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 204,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 204,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
185
cmd/loop/testdata/sessions/liquidity/11_loop-setrule-outgoing-type-in.json
vendored
Normal file
185
cmd/loop/testdata/sessions/liquidity/11_loop-setrule-outgoing-type-in.json
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setrule",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--type",
|
||||||
|
"in",
|
||||||
|
"--outgoing_threshold",
|
||||||
|
"30",
|
||||||
|
"124244814135296"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "rpc error: code = Unknown desc = channel level rules not supported for loop in swaps, only peer-level rules allowed",
|
||||||
|
"duration": 212373344,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 168,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "125344325763072",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 40,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 168,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "125344325763072",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 40,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_id": "124244814135296",
|
||||||
|
"swap_type": "LOOP_IN",
|
||||||
|
"pubkey": "",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 0,
|
||||||
|
"outgoing_threshold": 30
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 212,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "error",
|
||||||
|
"error": "rpc error: code = Unknown desc = channel level rules not supported for loop in swaps, only peer-level rules allowed",
|
||||||
|
"status": {
|
||||||
|
"code": 2,
|
||||||
|
"message": "channel level rules not supported for loop in swaps, only peer-level rules allowed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 212,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] rpc error: code = Unknown desc = channel level rules not supported for loop in swaps, only peer-level rules allowed\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 212,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "rpc error: code = Unknown desc = channel level rules not supported for loop in swaps, only peer-level rules allowed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
151
cmd/loop/testdata/sessions/liquidity/12_loop-setrule-clear.json
vendored
Normal file
151
cmd/loop/testdata/sessions/liquidity/12_loop-setrule-clear.json
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setrule",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--clear",
|
||||||
|
"125344325763072"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 232507711,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 150,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "125344325763072",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 40,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 150,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 231,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 232,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
191
cmd/loop/testdata/sessions/liquidity/13_loop-setparams-many-flags-error.json
vendored
Normal file
191
cmd/loop/testdata/sessions/liquidity/13_loop-setparams-many-flags-error.json
vendored
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--maxswapfee",
|
||||||
|
"0.1",
|
||||||
|
"--maxroutingfee",
|
||||||
|
"0.1",
|
||||||
|
"--maxprepayfee",
|
||||||
|
"0.1",
|
||||||
|
"--maxprepay",
|
||||||
|
"1000",
|
||||||
|
"--maxminer",
|
||||||
|
"1000",
|
||||||
|
"--sweepconf",
|
||||||
|
"10",
|
||||||
|
"--failurebackoff",
|
||||||
|
"60",
|
||||||
|
"--autoloop",
|
||||||
|
"--autobudget",
|
||||||
|
"1000",
|
||||||
|
"--autobudgetrefreshperiod",
|
||||||
|
"1h",
|
||||||
|
"--autoinflight",
|
||||||
|
"2",
|
||||||
|
"--minamt",
|
||||||
|
"1000",
|
||||||
|
"--maxamt",
|
||||||
|
"200000",
|
||||||
|
"--htlc_conf",
|
||||||
|
"2",
|
||||||
|
"--easyautoloop",
|
||||||
|
"--localbalancesat",
|
||||||
|
"1000000",
|
||||||
|
"--easyautoloop_excludepeer",
|
||||||
|
"0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7",
|
||||||
|
"--fast"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "rpc error: code = Unknown desc = sweep fee rate limit must be > 1 sat/vByte",
|
||||||
|
"duration": 325173938,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 276,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 276,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "1000",
|
||||||
|
"max_swap_amount": "200000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [
|
||||||
|
"AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3"
|
||||||
|
],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 324,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "error",
|
||||||
|
"error": "rpc error: code = Unknown desc = sweep fee rate limit must be > 1 sat/vByte",
|
||||||
|
"status": {
|
||||||
|
"code": 2,
|
||||||
|
"message": "sweep fee rate limit must be > 1 sat/vByte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 325,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] rpc error: code = Unknown desc = sweep fee rate limit must be > 1 sat/vByte\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 325,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "rpc error: code = Unknown desc = sweep fee rate limit must be > 1 sat/vByte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
193
cmd/loop/testdata/sessions/liquidity/14_loop-setparams-many-flags-error-minamt.json
vendored
Normal file
193
cmd/loop/testdata/sessions/liquidity/14_loop-setparams-many-flags-error-minamt.json
vendored
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--sweeplimit",
|
||||||
|
"2",
|
||||||
|
"--maxswapfee",
|
||||||
|
"0.1",
|
||||||
|
"--maxroutingfee",
|
||||||
|
"0.1",
|
||||||
|
"--maxprepayfee",
|
||||||
|
"0.1",
|
||||||
|
"--maxprepay",
|
||||||
|
"1000",
|
||||||
|
"--maxminer",
|
||||||
|
"1000",
|
||||||
|
"--sweepconf",
|
||||||
|
"10",
|
||||||
|
"--failurebackoff",
|
||||||
|
"60",
|
||||||
|
"--autoloop",
|
||||||
|
"--autobudget",
|
||||||
|
"1000",
|
||||||
|
"--autobudgetrefreshperiod",
|
||||||
|
"1h",
|
||||||
|
"--autoinflight",
|
||||||
|
"2",
|
||||||
|
"--minamt",
|
||||||
|
"1000",
|
||||||
|
"--maxamt",
|
||||||
|
"200000",
|
||||||
|
"--htlc_conf",
|
||||||
|
"2",
|
||||||
|
"--easyautoloop",
|
||||||
|
"--localbalancesat",
|
||||||
|
"1000000",
|
||||||
|
"--easyautoloop_excludepeer",
|
||||||
|
"0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7",
|
||||||
|
"--fast"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "rpc error: code = Unknown desc = minimum swap amount is less than server minimum",
|
||||||
|
"duration": 273083406,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 191,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 191,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "1000",
|
||||||
|
"max_swap_amount": "200000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [
|
||||||
|
"AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3"
|
||||||
|
],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 272,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "error",
|
||||||
|
"error": "rpc error: code = Unknown desc = minimum swap amount is less than server minimum",
|
||||||
|
"status": {
|
||||||
|
"code": 2,
|
||||||
|
"message": "minimum swap amount is less than server minimum"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 272,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] rpc error: code = Unknown desc = minimum swap amount is less than server minimum\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 273,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "rpc error: code = Unknown desc = minimum swap amount is less than server minimum"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
178
cmd/loop/testdata/sessions/liquidity/15_loop-setparams-many-flags.json
vendored
Normal file
178
cmd/loop/testdata/sessions/liquidity/15_loop-setparams-many-flags.json
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--sweeplimit",
|
||||||
|
"2",
|
||||||
|
"--maxswapfee",
|
||||||
|
"0.1",
|
||||||
|
"--maxroutingfee",
|
||||||
|
"0.1",
|
||||||
|
"--maxprepayfee",
|
||||||
|
"0.1",
|
||||||
|
"--maxprepay",
|
||||||
|
"1000",
|
||||||
|
"--maxminer",
|
||||||
|
"1000",
|
||||||
|
"--sweepconf",
|
||||||
|
"10",
|
||||||
|
"--failurebackoff",
|
||||||
|
"60",
|
||||||
|
"--autoloop",
|
||||||
|
"--autobudget",
|
||||||
|
"1000",
|
||||||
|
"--autobudgetrefreshperiod",
|
||||||
|
"1h",
|
||||||
|
"--autoinflight",
|
||||||
|
"2",
|
||||||
|
"--minamt",
|
||||||
|
"500000",
|
||||||
|
"--maxamt",
|
||||||
|
"1000000",
|
||||||
|
"--htlc_conf",
|
||||||
|
"2",
|
||||||
|
"--easyautoloop",
|
||||||
|
"--localbalancesat",
|
||||||
|
"1000000",
|
||||||
|
"--easyautoloop_excludepeer",
|
||||||
|
"0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7",
|
||||||
|
"--fast"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 184771728,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 160,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 161,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "500000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [
|
||||||
|
"AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3"
|
||||||
|
],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 182,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 184,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
147
cmd/loop/testdata/sessions/liquidity/16_loop-setparams-destaddr.json
vendored
Normal file
147
cmd/loop/testdata/sessions/liquidity/16_loop-setparams-destaddr.json
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--destaddr",
|
||||||
|
"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 235471909,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 223,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "500000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [
|
||||||
|
"AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3"
|
||||||
|
],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 224,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "500000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [
|
||||||
|
"AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3"
|
||||||
|
],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 234,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 235,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
149
cmd/loop/testdata/sessions/liquidity/17_loop-setparams-account.json
vendored
Normal file
149
cmd/loop/testdata/sessions/liquidity/17_loop-setparams-account.json
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--account",
|
||||||
|
"default",
|
||||||
|
"--account_addr_type",
|
||||||
|
"p2tr"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 264493138,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 249,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "500000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [
|
||||||
|
"AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3"
|
||||||
|
],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 250,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "500000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "default",
|
||||||
|
"account_addr_type": "TAPROOT_PUBKEY",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [
|
||||||
|
"AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3"
|
||||||
|
],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 264,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 264,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
144
cmd/loop/testdata/sessions/liquidity/18_loop-setparams-includeallpeers.json
vendored
Normal file
144
cmd/loop/testdata/sessions/liquidity/18_loop-setparams-includeallpeers.json
vendored
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--easyatutoloop_includeallpeers"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 304215261,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 263,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "500000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "default",
|
||||||
|
"account_addr_type": "TAPROOT_PUBKEY",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [
|
||||||
|
"AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3"
|
||||||
|
],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 271,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "500000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770076824",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "default",
|
||||||
|
"account_addr_type": "TAPROOT_PUBKEY",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 303,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 304,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
154
cmd/loop/testdata/sessions/liquidity/19_loop-setrule-type-out.json
vendored
Normal file
154
cmd/loop/testdata/sessions/liquidity/19_loop-setrule-type-out.json
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setrule",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--type",
|
||||||
|
"out",
|
||||||
|
"--incoming_threshold",
|
||||||
|
"20",
|
||||||
|
"125344325763072"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 195037901,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 137,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "500000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770092424",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "default",
|
||||||
|
"account_addr_type": "TAPROOT_PUBKEY",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 137,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 10,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"channel_id": "125344325763072",
|
||||||
|
"swap_type": "LOOP_OUT",
|
||||||
|
"pubkey": "",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 20,
|
||||||
|
"outgoing_threshold": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "0",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "2",
|
||||||
|
"max_swap_fee_ppm": "1000",
|
||||||
|
"max_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_routing_fee_ppm": "1000",
|
||||||
|
"max_prepay_sat": "1000",
|
||||||
|
"max_miner_fee_sat": "1000",
|
||||||
|
"sweep_conf_target": 10,
|
||||||
|
"failure_backoff_sec": "60",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "1000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "2",
|
||||||
|
"min_swap_amount": "500000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 2,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "3600",
|
||||||
|
"autoloop_budget_last_refresh": "1770092424",
|
||||||
|
"easy_autoloop": true,
|
||||||
|
"easy_autoloop_local_target_sat": "1000000",
|
||||||
|
"account": "default",
|
||||||
|
"account_addr_type": "TAPROOT_PUBKEY",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 194,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 195,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
36
cmd/loop/testdata/sessions/loopin/01_loop-in-invalid-amount.json
vendored
Normal file
36
cmd/loop/testdata/sessions/loopin/01_loop-in-invalid-amount.json
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"in",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"notanumber"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "invalid amt value \"notanumber\"",
|
||||||
|
"duration": 3964079,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] invalid amt value \"notanumber\"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "invalid amt value \"notanumber\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
39
cmd/loop/testdata/sessions/loopin/02_loop-in-external-conf-target.json
vendored
Normal file
39
cmd/loop/testdata/sessions/loopin/02_loop-in-external-conf-target.json
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"in",
|
||||||
|
"--external",
|
||||||
|
"--conf_target",
|
||||||
|
"6",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "external and conf_target both set",
|
||||||
|
"duration": 13741983,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 13,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] external and conf_target both set\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 13,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "external and conf_target both set"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
39
cmd/loop/testdata/sessions/loopin/03_loop-in-route-hints-private.json
vendored
Normal file
39
cmd/loop/testdata/sessions/loopin/03_loop-in-route-hints-private.json
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"in",
|
||||||
|
"--route_hints",
|
||||||
|
"{}",
|
||||||
|
"--private",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"10000"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "private and route_hints both set",
|
||||||
|
"duration": 2834558,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] private and route_hints both set\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "private and route_hints both set"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
101
cmd/loop/testdata/sessions/loopin/04_loop-in-external-cancel.json
vendored
Normal file
101
cmd/loop/testdata/sessions/loopin/04_loop-in-external-cancel.json
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"in",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--external",
|
||||||
|
"--amt",
|
||||||
|
"50000",
|
||||||
|
"--last_hop",
|
||||||
|
"0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "swap canceled",
|
||||||
|
"duration": 539895736,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "n\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "50000",
|
||||||
|
"conf_target": 0,
|
||||||
|
"external_htlc": true,
|
||||||
|
"swap_publication_deadline": "0",
|
||||||
|
"loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 539,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "1801",
|
||||||
|
"htlc_publish_fee_sat": "0",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 0,
|
||||||
|
"quoted_amt": "50000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 539,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] swap canceled\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 539,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"On-chain fee for external loop in is not included.\n",
|
||||||
|
"Sufficient fees will need to be paid when constructing the transaction in the external wallet.\n",
|
||||||
|
"\n",
|
||||||
|
"Send on-chain: 50000 sat\n",
|
||||||
|
"Receive off-chain: 48199 sat\n",
|
||||||
|
"Loop service fee: 1801 sat\n",
|
||||||
|
"\n",
|
||||||
|
"CONTINUE SWAP? (y/n): "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 539,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "swap canceled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
104
cmd/loop/testdata/sessions/loopin/05_loop-in-external-verbose-cancel.json
vendored
Normal file
104
cmd/loop/testdata/sessions/loopin/05_loop-in-external-verbose-cancel.json
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"in",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--external",
|
||||||
|
"--verbose",
|
||||||
|
"--amt",
|
||||||
|
"50000",
|
||||||
|
"--last_hop",
|
||||||
|
"0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "swap canceled",
|
||||||
|
"duration": 164102130,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 0,
|
||||||
|
"kind": "stdin",
|
||||||
|
"data": {
|
||||||
|
"text": "n\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "50000",
|
||||||
|
"conf_target": 0,
|
||||||
|
"external_htlc": true,
|
||||||
|
"swap_publication_deadline": "0",
|
||||||
|
"loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 163,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "1801",
|
||||||
|
"htlc_publish_fee_sat": "0",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 0,
|
||||||
|
"quoted_amt": "50000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 163,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"On-chain fee for external loop in is not included.\n",
|
||||||
|
"Sufficient fees will need to be paid when constructing the transaction in the external wallet.\n",
|
||||||
|
"\n",
|
||||||
|
"Send on-chain: 50000 sat\n",
|
||||||
|
"Receive off-chain: 48199 sat\n",
|
||||||
|
"Loop service fee: 1801 sat\n",
|
||||||
|
"\n",
|
||||||
|
"CLTV expiry delta: 0 block\n",
|
||||||
|
"\n",
|
||||||
|
"CONTINUE SWAP? (y/n): "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 164,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] swap canceled\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 164,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "swap canceled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
116
cmd/loop/testdata/sessions/loopin/06_loop-in-external-force.json
vendored
Normal file
116
cmd/loop/testdata/sessions/loopin/06_loop-in-external-force.json
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"in",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--external",
|
||||||
|
"--amt",
|
||||||
|
"500000",
|
||||||
|
"--force"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 1067066878,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"conf_target": 0,
|
||||||
|
"external_htlc": true,
|
||||||
|
"swap_publication_deadline": "0",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 561,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "1824",
|
||||||
|
"htlc_publish_fee_sat": "0",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 0,
|
||||||
|
"quoted_amt": "500000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 561,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopIn",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.LoopInRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"max_swap_fee": "1824",
|
||||||
|
"max_miner_fee": "0",
|
||||||
|
"last_hop": "",
|
||||||
|
"external_htlc": true,
|
||||||
|
"htlc_conf_target": 0,
|
||||||
|
"label": "",
|
||||||
|
"initiator": "loop-cli",
|
||||||
|
"route_hints": [],
|
||||||
|
"private": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1065,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopIn",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SwapResponse",
|
||||||
|
"payload": {
|
||||||
|
"id": "2f46a232e0f584fadf2abc7e7aaeefab0176f21e0fba5344a10e21838ec390fe",
|
||||||
|
"id_bytes": "L0aiMuD1hPrfKrx+eq7vqwF28h4PulNEoQ4hg47DkP4=",
|
||||||
|
"htlc_address": "bcrt1pcred99xc9l2cxphneej4ph6d6mczc0442d6ulyh3a9ad389z2aaq4n775y",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1pcred99xc9l2cxphneej4ph6d6mczc0442d6ulyh3a9ad389z2aaq4n775y",
|
||||||
|
"server_message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1066,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Swap initiated\n",
|
||||||
|
"ID: 2f46a232e0f584fadf2abc7e7aaeefab0176f21e0fba5344a10e21838ec390fe\n",
|
||||||
|
"HTLC address (P2TR): bcrt1pcred99xc9l2cxphneej4ph6d6mczc0442d6ulyh3a9ad389z2aaq4n775y\n",
|
||||||
|
"\n",
|
||||||
|
"Run `loop monitor` to monitor progress.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1067,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
127
cmd/loop/testdata/sessions/loopout/01_loop-out.json
vendored
Normal file
127
cmd/loop/testdata/sessions/loopout/01_loop-out.json
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--force",
|
||||||
|
"--fast",
|
||||||
|
"500000"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 228730686,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"conf_target": 9,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "1769407086",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 139,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.OutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "3875",
|
||||||
|
"prepay_amt_sat": "1337",
|
||||||
|
"htlc_sweep_fee_sat": "3793",
|
||||||
|
"swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 9,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 139,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.LoopOutRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"dest": "",
|
||||||
|
"max_swap_routing_fee": "10010",
|
||||||
|
"max_prepay_routing_fee": "36",
|
||||||
|
"max_swap_fee": "3875",
|
||||||
|
"max_prepay_amt": "1337",
|
||||||
|
"max_miner_fee": "948250",
|
||||||
|
"loop_out_channel": "0",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"sweep_conf_target": 9,
|
||||||
|
"htlc_confirmations": 1,
|
||||||
|
"swap_publication_deadline": "1769407086",
|
||||||
|
"label": "",
|
||||||
|
"initiator": "loop-cli",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"is_external_addr": false,
|
||||||
|
"reservation_ids": [],
|
||||||
|
"payment_timeout": 0,
|
||||||
|
"asset_info": null,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 228,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SwapResponse",
|
||||||
|
"payload": {
|
||||||
|
"id": "46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b",
|
||||||
|
"id_bytes": "RrjbZBEKYAi9NrQjQ5y7oejnbAJk6KaPRC/CKjwrT2s=",
|
||||||
|
"htlc_address": "bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv",
|
||||||
|
"server_message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 228,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Swap initiated\n",
|
||||||
|
"ID: 46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\n",
|
||||||
|
"\n",
|
||||||
|
"Run `loop monitor` to monitor progress.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 228,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
36
cmd/loop/testdata/sessions/loopout/02_loop-out-invalid-amount.json
vendored
Normal file
36
cmd/loop/testdata/sessions/loopout/02_loop-out-invalid-amount.json
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"notanumber"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "invalid amt value \"notanumber\"",
|
||||||
|
"duration": 1215843,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] invalid amt value \"notanumber\"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "invalid amt value \"notanumber\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
40
cmd/loop/testdata/sessions/loopout/03_loop-out-addr-account.json
vendored
Normal file
40
cmd/loop/testdata/sessions/loopout/03_loop-out-addr-account.json
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"out",
|
||||||
|
"--addr",
|
||||||
|
"bcrt1qtest",
|
||||||
|
"--account",
|
||||||
|
"testacct",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"50000"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "cannot set --addr and --account at the same time. Please specify only one source for a new address to sweep the loop amount to",
|
||||||
|
"duration": 1276860,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] cannot set --addr and --account at the same time. Please specify only one source for a new address to sweep the loop amount to\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "cannot set --addr and --account at the same time. Please specify only one source for a new address to sweep the loop amount to"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
40
cmd/loop/testdata/sessions/loopout/04_loop-out-invalid-account-addr-type.json
vendored
Normal file
40
cmd/loop/testdata/sessions/loopout/04_loop-out-invalid-account-addr-type.json
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"out",
|
||||||
|
"--account",
|
||||||
|
"testacct",
|
||||||
|
"--account_addr_type",
|
||||||
|
"foo",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"50000"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"run_error": "unknown account address type",
|
||||||
|
"duration": 1107998,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] unknown account address type\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "unknown account address type"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
135
cmd/loop/testdata/sessions/loopout/05_loop-out-amt-channel-maxfee-timeout.json
vendored
Normal file
135
cmd/loop/testdata/sessions/loopout/05_loop-out-amt-channel-maxfee-timeout.json
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--amt",
|
||||||
|
"500000",
|
||||||
|
"--channel",
|
||||||
|
"125344325763072",
|
||||||
|
"--max_swap_routing_fee",
|
||||||
|
"1000",
|
||||||
|
"--payment_timeout",
|
||||||
|
"30s",
|
||||||
|
"--force"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 288562226,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"conf_target": 9,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 161,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.OutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "3873",
|
||||||
|
"prepay_amt_sat": "1337",
|
||||||
|
"htlc_sweep_fee_sat": "3791",
|
||||||
|
"swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 9,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 161,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.LoopOutRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"dest": "",
|
||||||
|
"max_swap_routing_fee": "1000",
|
||||||
|
"max_prepay_routing_fee": "36",
|
||||||
|
"max_swap_fee": "3873",
|
||||||
|
"max_prepay_amt": "1337",
|
||||||
|
"max_miner_fee": "947750",
|
||||||
|
"loop_out_channel": "0",
|
||||||
|
"outgoing_chan_set": [
|
||||||
|
"125344325763072"
|
||||||
|
],
|
||||||
|
"sweep_conf_target": 9,
|
||||||
|
"htlc_confirmations": 1,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"label": "",
|
||||||
|
"initiator": "loop-cli",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"is_external_addr": false,
|
||||||
|
"reservation_ids": [],
|
||||||
|
"payment_timeout": 30,
|
||||||
|
"asset_info": null,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 288,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SwapResponse",
|
||||||
|
"payload": {
|
||||||
|
"id": "a564cdea5bac2fa1e350da6e7096ddd3bc0256c157f9f4b9b71a1262742f4f0d",
|
||||||
|
"id_bytes": "pWTN6lusL6HjUNpucJbd07wCVsFX+fS5txoSYnQvTw0=",
|
||||||
|
"htlc_address": "bcrt1pg84j6m04s6ecvlgput74dh4kfppn0lxn7f9h5cyl7vylyu3l55ss2f9xyh",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1pg84j6m04s6ecvlgput74dh4kfppn0lxn7f9h5cyl7vylyu3l55ss2f9xyh",
|
||||||
|
"server_message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 288,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Swap initiated\n",
|
||||||
|
"ID: a564cdea5bac2fa1e350da6e7096ddd3bc0256c157f9f4b9b71a1262742f4f0d\n",
|
||||||
|
"\n",
|
||||||
|
"Run `loop monitor` to monitor progress.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 288,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
129
cmd/loop/testdata/sessions/loopout/06_loop-out-addr-flag.json
vendored
Normal file
129
cmd/loop/testdata/sessions/loopout/06_loop-out-addr-flag.json
vendored
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--amt",
|
||||||
|
"500000",
|
||||||
|
"--addr",
|
||||||
|
"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw",
|
||||||
|
"--force"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 299201197,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"conf_target": 9,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 230,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.OutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "3873",
|
||||||
|
"prepay_amt_sat": "1337",
|
||||||
|
"htlc_sweep_fee_sat": "3791",
|
||||||
|
"swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 9,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 230,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.LoopOutRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"dest": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw",
|
||||||
|
"max_swap_routing_fee": "10010",
|
||||||
|
"max_prepay_routing_fee": "36",
|
||||||
|
"max_swap_fee": "3873",
|
||||||
|
"max_prepay_amt": "1337",
|
||||||
|
"max_miner_fee": "947750",
|
||||||
|
"loop_out_channel": "0",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"sweep_conf_target": 9,
|
||||||
|
"htlc_confirmations": 1,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"label": "",
|
||||||
|
"initiator": "loop-cli",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"is_external_addr": true,
|
||||||
|
"reservation_ids": [],
|
||||||
|
"payment_timeout": 0,
|
||||||
|
"asset_info": null,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 298,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SwapResponse",
|
||||||
|
"payload": {
|
||||||
|
"id": "67712e10d785636ec4b19c0c3d0f7b7612c1de5ccb2cb81df94e32ff13914fab",
|
||||||
|
"id_bytes": "Z3EuENeFY27EsZwMPQ97dhLB3lzLLLgd+U4y/xORT6s=",
|
||||||
|
"htlc_address": "bcrt1p0fn96tdw5ngu9cpww0w8q96tcps6w7jezaf0xnw0wa7e5d5gxv3st47amw",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1p0fn96tdw5ngu9cpww0w8q96tcps6w7jezaf0xnw0wa7e5d5gxv3st47amw",
|
||||||
|
"server_message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 298,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Swap initiated\n",
|
||||||
|
"ID: 67712e10d785636ec4b19c0c3d0f7b7612c1de5ccb2cb81df94e32ff13914fab\n",
|
||||||
|
"\n",
|
||||||
|
"Run `loop monitor` to monitor progress.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 299,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
128
cmd/loop/testdata/sessions/loopout/07_loop-out-addr-positional.json
vendored
Normal file
128
cmd/loop/testdata/sessions/loopout/07_loop-out-addr-positional.json
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--amt",
|
||||||
|
"500000",
|
||||||
|
"--force",
|
||||||
|
"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 290631176,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"conf_target": 9,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 205,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.OutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "3873",
|
||||||
|
"prepay_amt_sat": "1337",
|
||||||
|
"htlc_sweep_fee_sat": "3791",
|
||||||
|
"swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 9,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 205,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.LoopOutRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"dest": "",
|
||||||
|
"max_swap_routing_fee": "10010",
|
||||||
|
"max_prepay_routing_fee": "36",
|
||||||
|
"max_swap_fee": "3873",
|
||||||
|
"max_prepay_amt": "1337",
|
||||||
|
"max_miner_fee": "947750",
|
||||||
|
"loop_out_channel": "0",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"sweep_conf_target": 9,
|
||||||
|
"htlc_confirmations": 1,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"label": "",
|
||||||
|
"initiator": "loop-cli",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"is_external_addr": false,
|
||||||
|
"reservation_ids": [],
|
||||||
|
"payment_timeout": 0,
|
||||||
|
"asset_info": null,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 290,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SwapResponse",
|
||||||
|
"payload": {
|
||||||
|
"id": "aa7d0c558627aaa8af70503cd81d4679400cc9771063cb474c32c625d5f2a8f3",
|
||||||
|
"id_bytes": "qn0MVYYnqqivcFA82B1GeUAMyXcQY8tHTDLGJdXyqPM=",
|
||||||
|
"htlc_address": "bcrt1p9jhv4amddjazuyzs8xr2u2fzzqdt2s5tx95ffhd43avxvq7d0xxsykf5r5",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1p9jhv4amddjazuyzs8xr2u2fzzqdt2s5tx95ffhd43avxvq7d0xxsykf5r5",
|
||||||
|
"server_message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 290,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Swap initiated\n",
|
||||||
|
"ID: aa7d0c558627aaa8af70503cd81d4679400cc9771063cb474c32c625d5f2a8f3\n",
|
||||||
|
"\n",
|
||||||
|
"Run `loop monitor` to monitor progress.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 290,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
131
cmd/loop/testdata/sessions/loopout/08_loop-out-account.json
vendored
Normal file
131
cmd/loop/testdata/sessions/loopout/08_loop-out-account.json
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--amt",
|
||||||
|
"500000",
|
||||||
|
"--account",
|
||||||
|
"default",
|
||||||
|
"--account_addr_type",
|
||||||
|
"p2tr",
|
||||||
|
"--force"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 466934696,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"conf_target": 9,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 360,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.OutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "3873",
|
||||||
|
"prepay_amt_sat": "1337",
|
||||||
|
"htlc_sweep_fee_sat": "3791",
|
||||||
|
"swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 9,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 360,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.LoopOutRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"dest": "",
|
||||||
|
"max_swap_routing_fee": "10010",
|
||||||
|
"max_prepay_routing_fee": "36",
|
||||||
|
"max_swap_fee": "3873",
|
||||||
|
"max_prepay_amt": "1337",
|
||||||
|
"max_miner_fee": "947750",
|
||||||
|
"loop_out_channel": "0",
|
||||||
|
"outgoing_chan_set": [],
|
||||||
|
"sweep_conf_target": 9,
|
||||||
|
"htlc_confirmations": 1,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"label": "",
|
||||||
|
"initiator": "loop-cli",
|
||||||
|
"account": "default",
|
||||||
|
"account_addr_type": "TAPROOT_PUBKEY",
|
||||||
|
"is_external_addr": false,
|
||||||
|
"reservation_ids": [],
|
||||||
|
"payment_timeout": 0,
|
||||||
|
"asset_info": null,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 462,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOut",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SwapResponse",
|
||||||
|
"payload": {
|
||||||
|
"id": "04c1ef677f170f50ad3f221f49a779b99a464907bde2e0003a7b1dd6402d1419",
|
||||||
|
"id_bytes": "BMHvZ38XD1CtPyIfSad5uZpGSQe94uAAOnsd1kAtFBk=",
|
||||||
|
"htlc_address": "bcrt1p8lvg7l4zkdu0l4pe74h96240ndmp3demreml3zeqy28dvgq7j95q23zqcy",
|
||||||
|
"htlc_address_p2wsh": "",
|
||||||
|
"htlc_address_p2tr": "bcrt1p8lvg7l4zkdu0l4pe74h96240ndmp3demreml3zeqy28dvgq7j95q23zqcy",
|
||||||
|
"server_message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 462,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Swap initiated\n",
|
||||||
|
"ID: 04c1ef677f170f50ad3f221f49a779b99a464907bde2e0003a7b1dd6402d1419\n",
|
||||||
|
"\n",
|
||||||
|
"Run `loop monitor` to monitor progress.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 466,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
103
cmd/loop/testdata/sessions/misc/01_loop-terms.json
vendored
Normal file
103
cmd/loop/testdata/sessions/misc/01_loop-terms.json
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"terms",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 199188467,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 7,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Loop Out\n",
|
||||||
|
"--------\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 7,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutTerms",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.TermsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 156,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutTerms",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.OutTermsResponse",
|
||||||
|
"payload": {
|
||||||
|
"min_swap_amount": "50000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"min_cltv_delta": 50,
|
||||||
|
"max_cltv_delta": 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 156,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Amount: 50000 - 1000000\n",
|
||||||
|
"Cltv delta: 50 - 250\n",
|
||||||
|
"\n",
|
||||||
|
"Loop In\n",
|
||||||
|
"------\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 156,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInTerms",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.TermsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 198,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInTerms",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InTermsResponse",
|
||||||
|
"payload": {
|
||||||
|
"min_swap_amount": "50000",
|
||||||
|
"max_swap_amount": "1000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 198,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Amount: 50000 - 1000000\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 199,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
76
cmd/loop/testdata/sessions/quote/01_loop-quote-out.json
vendored
Normal file
76
cmd/loop/testdata/sessions/quote/01_loop-quote-out.json
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"quote",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"500000"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 159166421,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "500000",
|
||||||
|
"conf_target": 9,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 158,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.OutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "3875",
|
||||||
|
"prepay_amt_sat": "1337",
|
||||||
|
"htlc_sweep_fee_sat": "3793",
|
||||||
|
"swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 9,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 158,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Send off-chain: 500000 sat\n",
|
||||||
|
"Receive on-chain: 492332 sat\n",
|
||||||
|
"Estimated total fee: 7668 sat\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 159,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
92
cmd/loop/testdata/sessions/quote/02_loop-quote-in-help.json
vendored
Normal file
92
cmd/loop/testdata/sessions/quote/02_loop-quote-in-help.json
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"quote",
|
||||||
|
"in",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 1248128,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"NAME:\n",
|
||||||
|
" loop quote in - get a quote for the cost of a loop in swap\n",
|
||||||
|
"\n",
|
||||||
|
"USAGE:\n",
|
||||||
|
" loop quote in amt\n",
|
||||||
|
"\n",
|
||||||
|
"DESCRIPTION:\n",
|
||||||
|
" Allows to determine the cost of a swap up front.Either specify an amount or deposit outpoints.\n",
|
||||||
|
"\n",
|
||||||
|
"OPTIONS:\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
" --last_hop string the pubkey of the last hop to use for the quote\n",
|
||||||
|
" --conf_target uint the target number of blocks the on-chain htlc broadcast by the swap client should confirm within (default: 0)\n",
|
||||||
|
" --verbose, -v show expanded details (default: false)\n",
|
||||||
|
" --private generates and passes routehints. Should be used if the connected node is only reachable via private channels (default: false)\n",
|
||||||
|
" --route_hints string [ --route_hints string ] "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
" route hints that can each be individually used to assist in reaching the invoice's destination\n",
|
||||||
|
" --deposit_outpoint string [ --deposit_outpoint string ] one or more static address deposit outpoints to quote for. Deposit outpoints are not to be used in combination with an amount. Eachadditional outpoint can be added by specifying --deposit_outpoint tx_id:idx\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
" --help, -h "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
" "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"show help\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
55
cmd/loop/testdata/sessions/quote/03_loop-quote-out-help.json
vendored
Normal file
55
cmd/loop/testdata/sessions/quote/03_loop-quote-out-help.json
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"quote",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 1687027,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"NAME:"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"\n",
|
||||||
|
" loop quote out - get a quote for the cost of a loop out swap\n",
|
||||||
|
"\n",
|
||||||
|
"USAGE:\n",
|
||||||
|
" loop quote out amt\n",
|
||||||
|
"\n",
|
||||||
|
"DESCRIPTION:\n",
|
||||||
|
" Allows to determine the cost of a swap up front\n",
|
||||||
|
"\n",
|
||||||
|
"OPTIONS:\n",
|
||||||
|
" --conf_target uint the number of blocks from the swap initiation height that the on-chain HTLC should be swept within in a Loop Out (default: 9)\n",
|
||||||
|
" --fast Indicate you want to swap immediately, paying potentially a higher fee. If not set the swap server might choose to wait up to 30 minutes before publishing the swap HTLC on-chain, to save on chain fees. Not setting this flag might result in a lower swap fee. (default: false)\n",
|
||||||
|
" --verbose, -v show expanded details (default: false)\n",
|
||||||
|
" --help, -h show help\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
114
cmd/loop/testdata/sessions/quote/04_loop-quote-in.json
vendored
Normal file
114
cmd/loop/testdata/sessions/quote/04_loop-quote-in.json
vendored
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"quote",
|
||||||
|
"in",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--deposit_outpoint",
|
||||||
|
"edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 622954327,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsRequest",
|
||||||
|
"payload": {
|
||||||
|
"state_filter": "UNKNOWN_STATE",
|
||||||
|
"outpoints": [
|
||||||
|
"edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 165,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsResponse",
|
||||||
|
"payload": {
|
||||||
|
"filtered_deposits": [
|
||||||
|
{
|
||||||
|
"id": "aCYqEEyewyXea+w3uOMb2HW70vXwuc4togzwvWNvxEg=",
|
||||||
|
"state": "DEPOSITED",
|
||||||
|
"outpoint": "edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0",
|
||||||
|
"value": "500000",
|
||||||
|
"confirmation_height": "125",
|
||||||
|
"blocks_until_expiry": "14394",
|
||||||
|
"swap_hash": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 165,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "0",
|
||||||
|
"conf_target": 0,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "0",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [
|
||||||
|
"edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0"
|
||||||
|
],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 622,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "1825",
|
||||||
|
"htlc_publish_fee_sat": "0",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 0,
|
||||||
|
"quoted_amt": "500000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 622,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Previously deposited on-chain: 500000 sat\n",
|
||||||
|
"Receive off-chain: 498175 sat\n",
|
||||||
|
"Estimated total fee: 1825 sat\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 622,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
76
cmd/loop/testdata/sessions/quote/05_loop-quote-in-last-hop.json
vendored
Normal file
76
cmd/loop/testdata/sessions/quote/05_loop-quote-in-last-hop.json
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"quote",
|
||||||
|
"in",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--last_hop",
|
||||||
|
"0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7",
|
||||||
|
"50000"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 226964878,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "50000",
|
||||||
|
"conf_target": 0,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "0",
|
||||||
|
"loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 226,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "1801",
|
||||||
|
"htlc_publish_fee_sat": "3873",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 6,
|
||||||
|
"quoted_amt": "50000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 226,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Send on-chain: 50000 sat\n",
|
||||||
|
"Receive off-chain: 44326 sat\n",
|
||||||
|
"Estimated total fee: 5674 sat\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 226,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
93
cmd/loop/testdata/sessions/quote/06_loop-quote-out-verbose.json
vendored
Normal file
93
cmd/loop/testdata/sessions/quote/06_loop-quote-out-verbose.json
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"quote",
|
||||||
|
"out",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--verbose",
|
||||||
|
"50000"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 273326957,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "50000",
|
||||||
|
"conf_target": 9,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "1769408886",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 272,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/LoopOutQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.OutQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "3828",
|
||||||
|
"prepay_amt_sat": "1337",
|
||||||
|
"htlc_sweep_fee_sat": "3791",
|
||||||
|
"swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 9,
|
||||||
|
"asset_rfq_info": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 273,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Send off-chain: 50000 sat\n",
|
||||||
|
"Receive on-chain: 42381 sat\n",
|
||||||
|
"\n",
|
||||||
|
"Estimated on-chain fee: 3791 sat\n",
|
||||||
|
"Loop service fee: 3828 sat\n",
|
||||||
|
"Estimated total fee: 7619 sat\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 273,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"No show penalty (prepay): 1337 sat\n",
|
||||||
|
"Conf target: 9 block\n",
|
||||||
|
"CLTV expiry delta: 0 block\n",
|
||||||
|
"Publication deadline: 2026-01-26 01:28:06 -0500 EST\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 273,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
cmd/loop/testdata/sessions/quote/07_loop-quote-in-verbose.json
vendored
Normal file
81
cmd/loop/testdata/sessions/quote/07_loop-quote-in-verbose.json
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"quote",
|
||||||
|
"in",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--verbose",
|
||||||
|
"50000"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 380680881,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.QuoteRequest",
|
||||||
|
"payload": {
|
||||||
|
"amt": "50000",
|
||||||
|
"conf_target": 0,
|
||||||
|
"external_htlc": false,
|
||||||
|
"swap_publication_deadline": "0",
|
||||||
|
"loop_in_last_hop": "",
|
||||||
|
"loop_in_route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"deposit_outpoints": [],
|
||||||
|
"asset_info": null,
|
||||||
|
"auto_select_deposits": false,
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 379,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLoopInQuote",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.InQuoteResponse",
|
||||||
|
"payload": {
|
||||||
|
"swap_fee_sat": "1800",
|
||||||
|
"htlc_publish_fee_sat": "3871",
|
||||||
|
"cltv_delta": 0,
|
||||||
|
"conf_target": 6,
|
||||||
|
"quoted_amt": "50000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 380,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"Send on-chain: 50000 sat\n",
|
||||||
|
"Receive off-chain: 44329 sat\n",
|
||||||
|
"\n",
|
||||||
|
"Estimated on-chain fee: 3871 sat\n",
|
||||||
|
"Loop service fee: 1800 sat\n",
|
||||||
|
"Estimated total fee: 5671 sat\n",
|
||||||
|
"\n",
|
||||||
|
"Conf target: 6 block\n",
|
||||||
|
"CLTV expiry delta: 0 block\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 380,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
151
cmd/loop/testdata/sessions/static-autoloop/01_loop-setparams-static-address.json
vendored
Normal file
151
cmd/loop/testdata/sessions/static-autoloop/01_loop-setparams-static-address.json
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--loopinsource",
|
||||||
|
"static-address",
|
||||||
|
"--minamt",
|
||||||
|
"250000",
|
||||||
|
"--maxamt",
|
||||||
|
"1000000",
|
||||||
|
"--feepercent",
|
||||||
|
"5",
|
||||||
|
"--autoloop",
|
||||||
|
"--autobudget",
|
||||||
|
"50000",
|
||||||
|
"--autoinflight",
|
||||||
|
"1"
|
||||||
|
],
|
||||||
|
"version": "0.33.0-beta commit=v0.33.0-beta-74-g6a3c3e1df5f807b450d3ba663f4824f6588e1c13 commit_hash=6a3c3e1df5f807b450d3ba663f4824f6588e1c13",
|
||||||
|
"duration": 32231921,
|
||||||
|
"clock_start_unix": 1779406641
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 10,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 16,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_IN",
|
||||||
|
"pubkey": "AgN4nvpxLgk471Ol2/D2BGUJ9l/FOlv8dRHcZ/6+oaFl",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 0,
|
||||||
|
"outgoing_threshold": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "50000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "50000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "250000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1779405910",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_STATIC_ADDRESS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 18,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_IN",
|
||||||
|
"pubkey": "AgN4nvpxLgk471Ol2/D2BGUJ9l/FOlv8dRHcZ/6+oaFl",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 0,
|
||||||
|
"outgoing_threshold": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "50000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "50000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "250000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1779405910",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_STATIC_ADDRESS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 31,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsResponse",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 32,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
125
cmd/loop/testdata/sessions/static-autoloop/02_loop-getparams-static-address.json
vendored
Normal file
125
cmd/loop/testdata/sessions/static-autoloop/02_loop-getparams-static-address.json
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"getparams",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"version": "0.33.0-beta commit=v0.33.0-beta-74-g6a3c3e1df5f807b450d3ba663f4824f6588e1c13 commit_hash=6a3c3e1df5f807b450d3ba663f4824f6588e1c13",
|
||||||
|
"duration": 12324887,
|
||||||
|
"clock_start_unix": 1779406647
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 5,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"channel_id": "0",
|
||||||
|
"swap_type": "LOOP_IN",
|
||||||
|
"pubkey": "AgN4nvpxLgk471Ol2/D2BGUJ9l/FOlv8dRHcZ/6+oaFl",
|
||||||
|
"type": "THRESHOLD",
|
||||||
|
"incoming_threshold": 0,
|
||||||
|
"outgoing_threshold": 60
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fee_ppm": "50000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": true,
|
||||||
|
"autoloop_budget_sat": "50000",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "250000",
|
||||||
|
"max_swap_amount": "1000000",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1779405910",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_STATIC_ADDRESS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 12,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"account\": \"\",\n",
|
||||||
|
" \"account_addr_type\": \"ADDRESS_TYPE_UNKNOWN\",\n",
|
||||||
|
" \"auto_max_in_flight\": \"1\",\n",
|
||||||
|
" \"autoloop\": true,\n",
|
||||||
|
" \"autoloop_budget_last_refresh\": \"1779405910\",\n",
|
||||||
|
" \"autoloop_budget_refresh_period_sec\": \"604800\",\n",
|
||||||
|
" \"autoloop_budget_sat\": \"50000\",\n",
|
||||||
|
" \"autoloop_budget_start_sec\": \"0\",\n",
|
||||||
|
" \"autoloop_dest_address\": \"\",\n",
|
||||||
|
" \"easy_asset_params\": {},\n",
|
||||||
|
" \"easy_autoloop\": false,\n",
|
||||||
|
" \"easy_autoloop_excluded_peers\": [],\n",
|
||||||
|
" \"easy_autoloop_local_target_sat\": \"0\",\n",
|
||||||
|
" \"failure_backoff_sec\": \"86400\",\n",
|
||||||
|
" \"fast_swap_publication\": true,\n",
|
||||||
|
" \"fee_ppm\": \"50000\",\n",
|
||||||
|
" \"htlc_conf_target\": 6,\n",
|
||||||
|
" \"loop_in_source\": \"LOOP_IN_SOURCE_STATIC_ADDRESS\",\n",
|
||||||
|
" \"max_miner_fee_sat\": \"0\",\n",
|
||||||
|
" \"max_prepay_routing_fee_ppm\": \"0\",\n",
|
||||||
|
" \"max_prepay_sat\": \"0\",\n",
|
||||||
|
" \"max_routing_fee_ppm\": \"0\",\n",
|
||||||
|
" \"max_swap_amount\": \"1000000\",\n",
|
||||||
|
" \"max_swap_fee_ppm\": \"0\",\n",
|
||||||
|
" \"min_swap_amount\": \"250000\",\n",
|
||||||
|
" \"rules\": [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"channel_id\": \"0\",\n",
|
||||||
|
" \"incoming_threshold\": 0,\n",
|
||||||
|
" \"outgoing_threshold\": 60,\n",
|
||||||
|
" \"pubkey\": \"0203789efa712e0938ef53a5dbf0f6046509f65fc53a5bfc7511dc67febea1a165\",\n",
|
||||||
|
" \"swap_type\": \"LOOP_IN\",\n",
|
||||||
|
" \"type\": \"THRESHOLD\"\n",
|
||||||
|
" }\n",
|
||||||
|
" ],\n",
|
||||||
|
" \"sweep_conf_target\": 100,\n",
|
||||||
|
" \"sweep_fee_rate_sat_per_vbyte\": \"0\"\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 12,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
101
cmd/loop/testdata/sessions/static-autoloop/03_loop-suggestswaps-static-loop-in.json
vendored
Normal file
101
cmd/loop/testdata/sessions/static-autoloop/03_loop-suggestswaps-static-loop-in.json
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"suggestswaps",
|
||||||
|
"--network",
|
||||||
|
"regtest"
|
||||||
|
],
|
||||||
|
"version": "0.33.0-beta commit=v0.33.0-beta-74-g6a3c3e1df5f807b450d3ba663f4824f6588e1c13 commit_hash=6a3c3e1df5f807b450d3ba663f4824f6588e1c13",
|
||||||
|
"duration": 44264819,
|
||||||
|
"clock_start_unix": 1779406653
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SuggestSwaps",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SuggestSwapsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 43,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SuggestSwaps",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.SuggestSwapsResponse",
|
||||||
|
"payload": {
|
||||||
|
"loop_out": [],
|
||||||
|
"loop_in": [],
|
||||||
|
"static_loop_in": [
|
||||||
|
{
|
||||||
|
"outpoints": [
|
||||||
|
"9cfb0ec5ab2c851b4db5fe372cc3573f4074c436e50b13d2b7d214e45571def9:1"
|
||||||
|
],
|
||||||
|
"max_swap_fee_satoshis": "1828",
|
||||||
|
"last_hop": "AgN4nvpxLgk471Ol2/D2BGUJ9l/FOlv8dRHcZ/6+oaFl",
|
||||||
|
"label": "[reserved]: autoloop-in",
|
||||||
|
"initiator": "autoloop",
|
||||||
|
"route_hints": [],
|
||||||
|
"private": false,
|
||||||
|
"payment_timeout_seconds": 0,
|
||||||
|
"amount": "500000",
|
||||||
|
"fast": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"disqualified": [
|
||||||
|
{
|
||||||
|
"channel_id": "127543348887552",
|
||||||
|
"pubkey": "",
|
||||||
|
"reason": "AUTO_REASON_CUSTOM_CHANNEL_DATA"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 43,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"disqualified\": [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"channel_id\": \"127543348887552\",\n",
|
||||||
|
" \"pubkey\": \"\",\n",
|
||||||
|
" \"reason\": \"AUTO_REASON_CUSTOM_CHANNEL_DATA\"\n",
|
||||||
|
" }\n",
|
||||||
|
" ],\n",
|
||||||
|
" \"loop_in\": [],\n",
|
||||||
|
" \"loop_out\": [],\n",
|
||||||
|
" \"static_loop_in\": [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"amount\": \"500000\",\n",
|
||||||
|
" \"fast\": false,\n",
|
||||||
|
" \"initiator\": \"autoloop\",\n",
|
||||||
|
" \"label\": \"[reserved]: autoloop-in\",\n",
|
||||||
|
" \"last_hop\": \"0203789efa712e0938ef53a5dbf0f6046509f65fc53a5bfc7511dc67febea1a165\",\n",
|
||||||
|
" \"max_swap_fee_satoshis\": \"1828\",\n",
|
||||||
|
" \"outpoints\": [\n",
|
||||||
|
" \"9cfb0ec5ab2c851b4db5fe372cc3573f4074c436e50b13d2b7d214e45571def9:1\"\n",
|
||||||
|
" ],\n",
|
||||||
|
" \"payment_timeout_seconds\": 0,\n",
|
||||||
|
" \"private\": false,\n",
|
||||||
|
" \"route_hints\": []\n",
|
||||||
|
" }\n",
|
||||||
|
" ]\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 44,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
137
cmd/loop/testdata/sessions/static-autoloop/04_loop-setparams-static-address-no-experimental.json
vendored
Normal file
137
cmd/loop/testdata/sessions/static-autoloop/04_loop-setparams-static-address-no-experimental.json
vendored
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"setparams",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--loopinsource",
|
||||||
|
"static-address"
|
||||||
|
],
|
||||||
|
"version": "0.33.0-beta commit=v0.33.0-beta-74-g6a3c3e1df5f807b450d3ba663f4824f6588e1c13 commit_hash=6a3c3e1df5f807b450d3ba663f4824f6588e1c13",
|
||||||
|
"run_error": "rpc error: code = FailedPrecondition desc = static address autoloop requires --experimental",
|
||||||
|
"duration": 12501068,
|
||||||
|
"clock_start_unix": 1779406912
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 4,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.GetLiquidityParamsRequest",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 11,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/GetLiquidityParams",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.LiquidityParameters",
|
||||||
|
"payload": {
|
||||||
|
"rules": [],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1779406903",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_WALLET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 11,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.SetLiquidityParamsRequest",
|
||||||
|
"payload": {
|
||||||
|
"parameters": {
|
||||||
|
"rules": [],
|
||||||
|
"fee_ppm": "20000",
|
||||||
|
"sweep_fee_rate_sat_per_vbyte": "0",
|
||||||
|
"max_swap_fee_ppm": "0",
|
||||||
|
"max_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_routing_fee_ppm": "0",
|
||||||
|
"max_prepay_sat": "0",
|
||||||
|
"max_miner_fee_sat": "0",
|
||||||
|
"sweep_conf_target": 100,
|
||||||
|
"failure_backoff_sec": "86400",
|
||||||
|
"autoloop": false,
|
||||||
|
"autoloop_budget_sat": "335544",
|
||||||
|
"autoloop_budget_start_sec": "0",
|
||||||
|
"auto_max_in_flight": "1",
|
||||||
|
"min_swap_amount": "0",
|
||||||
|
"max_swap_amount": "0",
|
||||||
|
"htlc_conf_target": 6,
|
||||||
|
"autoloop_dest_address": "",
|
||||||
|
"autoloop_budget_refresh_period_sec": "604800",
|
||||||
|
"autoloop_budget_last_refresh": "1779406903",
|
||||||
|
"easy_autoloop": false,
|
||||||
|
"easy_autoloop_local_target_sat": "0",
|
||||||
|
"account": "",
|
||||||
|
"account_addr_type": "ADDRESS_TYPE_UNKNOWN",
|
||||||
|
"easy_asset_params": {},
|
||||||
|
"fast_swap_publication": true,
|
||||||
|
"easy_autoloop_excluded_peers": [],
|
||||||
|
"loop_in_source": "LOOP_IN_SOURCE_STATIC_ADDRESS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 12,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/SetLiquidityParams",
|
||||||
|
"event": "error",
|
||||||
|
"error": "rpc error: code = FailedPrecondition desc = static address autoloop requires --experimental",
|
||||||
|
"status": {
|
||||||
|
"code": 9,
|
||||||
|
"message": "static address autoloop requires --experimental"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 12,
|
||||||
|
"kind": "stderr",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"[loop] rpc error: code = FailedPrecondition desc = static address autoloop requires --experimental\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 12,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {
|
||||||
|
"run_error": "rpc error: code = FailedPrecondition desc = static address autoloop requires --experimental"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
62
cmd/loop/testdata/sessions/static-filters/01_loop-static-listdeposits-deposited.json
vendored
Normal file
62
cmd/loop/testdata/sessions/static-filters/01_loop-static-listdeposits-deposited.json
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"static",
|
||||||
|
"listdeposits",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--filter",
|
||||||
|
"deposited"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 304503708,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsRequest",
|
||||||
|
"payload": {
|
||||||
|
"state_filter": "DEPOSITED",
|
||||||
|
"outpoints": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 303,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsResponse",
|
||||||
|
"payload": {
|
||||||
|
"filtered_deposits": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 304,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"filtered_deposits\": []\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 304,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
82
cmd/loop/testdata/sessions/static-filters/02_loop-static-listdeposits-withdrawing.json
vendored
Normal file
82
cmd/loop/testdata/sessions/static-filters/02_loop-static-listdeposits-withdrawing.json
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"static",
|
||||||
|
"listdeposits",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--filter",
|
||||||
|
"withdrawing"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 210052149,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsRequest",
|
||||||
|
"payload": {
|
||||||
|
"state_filter": "WITHDRAWING",
|
||||||
|
"outpoints": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 209,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsResponse",
|
||||||
|
"payload": {
|
||||||
|
"filtered_deposits": [
|
||||||
|
{
|
||||||
|
"id": "u38FDfC3w+H+YQEOEK1F4w3fes0wH6bgWi3bgltcLvs=",
|
||||||
|
"state": "WITHDRAWING",
|
||||||
|
"outpoint": "56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313:0",
|
||||||
|
"value": "500000",
|
||||||
|
"confirmation_height": "160",
|
||||||
|
"blocks_until_expiry": "14395",
|
||||||
|
"swap_hash": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 209,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"filtered_deposits\": [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"blocks_until_expiry\": \"14395\",\n",
|
||||||
|
" \"confirmation_height\": \"160\",\n",
|
||||||
|
" \"id\": \"bb7f050df0b7c3e1fe61010e10ad45e30ddf7acd301fa6e05a2ddb825b5c2efb\",\n",
|
||||||
|
" \"outpoint\": \"56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313:0\",\n",
|
||||||
|
" \"state\": \"WITHDRAWING\",\n",
|
||||||
|
" \"swap_hash\": \"\",\n",
|
||||||
|
" \"value\": \"500000\"\n",
|
||||||
|
" }\n",
|
||||||
|
" ]\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 210,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
136
cmd/loop/testdata/sessions/static-filters/03_loop-static-listdeposits-withdrawn.json
vendored
Normal file
136
cmd/loop/testdata/sessions/static-filters/03_loop-static-listdeposits-withdrawn.json
vendored
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"static",
|
||||||
|
"listdeposits",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--filter",
|
||||||
|
"withdrawn"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 271219039,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsRequest",
|
||||||
|
"payload": {
|
||||||
|
"state_filter": "WITHDRAWN",
|
||||||
|
"outpoints": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 269,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsResponse",
|
||||||
|
"payload": {
|
||||||
|
"filtered_deposits": [
|
||||||
|
{
|
||||||
|
"id": "aCYqEEyewyXea+w3uOMb2HW70vXwuc4togzwvWNvxEg=",
|
||||||
|
"state": "WITHDRAWN",
|
||||||
|
"outpoint": "edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0",
|
||||||
|
"value": "500000",
|
||||||
|
"confirmation_height": "125",
|
||||||
|
"blocks_until_expiry": "14360",
|
||||||
|
"swap_hash": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hrXizflpTI5zmOQq/eEJdm180hQiA5Bbpj+9DrE3DvM=",
|
||||||
|
"state": "WITHDRAWN",
|
||||||
|
"outpoint": "bb358e4f73ae97c4e2d99c6d64e852bba7cf56e13105b05d1200b8ae1796665e:0",
|
||||||
|
"value": "500000",
|
||||||
|
"confirmation_height": "127",
|
||||||
|
"blocks_until_expiry": "14362",
|
||||||
|
"swap_hash": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bCkPdTbqUJeUav/6xqaZBqJtd1gj67rO3+by1pwHReQ=",
|
||||||
|
"state": "WITHDRAWN",
|
||||||
|
"outpoint": "5eaa7dd7a291665393eddf5dece91feef901f22665933cce7a0732a9b81c3001:0",
|
||||||
|
"value": "500000",
|
||||||
|
"confirmation_height": "128",
|
||||||
|
"blocks_until_expiry": "14363",
|
||||||
|
"swap_hash": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AYK02JWxxGcpCue1xsQv92sqQiWAepQhHJcxcNWog+s=",
|
||||||
|
"state": "WITHDRAWN",
|
||||||
|
"outpoint": "7e6360d6e6a394cfd096adf0bfe1275c5a83541eb573e90e463a78dc715f8894:0",
|
||||||
|
"value": "500000",
|
||||||
|
"confirmation_height": "142",
|
||||||
|
"blocks_until_expiry": "14377",
|
||||||
|
"swap_hash": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 269,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"filtered_deposits\": [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"blocks_until_expiry\": \"14360\",\n",
|
||||||
|
" \"confirmation_height\": \"125\",\n",
|
||||||
|
" \"id\": \"68262a104c9ec325de6bec37b8e31bd875bbd2f5f0b9ce2da20cf0bd636fc448\",\n",
|
||||||
|
" \"outpoint\": \"edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0\",\n",
|
||||||
|
" \"state\": \"WITHDRAWN\",\n",
|
||||||
|
" \"swap_hash\": \"\",\n",
|
||||||
|
" \"value\": \"500000\"\n",
|
||||||
|
" },\n",
|
||||||
|
" {\n",
|
||||||
|
" \"blocks_until_expiry\": \"14362\",\n",
|
||||||
|
" \"confirmation_height\": \"127\",\n",
|
||||||
|
" \"id\": \"86b5e2cdf9694c8e7398e42afde109766d7cd2142203905ba63fbd0eb1370ef3\",\n",
|
||||||
|
" \"outpoint\": \"bb358e4f73ae97c4e2d99c6d64e852bba7cf56e13105b05d1200b8ae1796665e:0\",\n",
|
||||||
|
" \"state\": \"WITHDRAWN\",\n",
|
||||||
|
" \"swap_hash\": \"\",\n",
|
||||||
|
" \"value\": \"500000\"\n",
|
||||||
|
" },\n",
|
||||||
|
" {\n",
|
||||||
|
" \"blocks_until_expiry\": \"14363\",\n",
|
||||||
|
" \"confirmation_height\": \"128\",\n",
|
||||||
|
" \"id\": \"6c290f7536ea5097946afffac6a69906a26d775823ebbacedfe6f2d69c0745e4\",\n",
|
||||||
|
" \"outpoint\": \"5eaa7dd7a291665393eddf5dece91feef901f22665933cce7a0732a9b81c3001:0\",\n",
|
||||||
|
" \"state\": \"WITHDRAWN\",\n",
|
||||||
|
" \"swap_hash\": \"\",\n",
|
||||||
|
" \"value\": \"500000\"\n",
|
||||||
|
" },\n",
|
||||||
|
" {\n",
|
||||||
|
" \"blocks_until_expiry\": \"14377\",\n",
|
||||||
|
" \"confirmation_height\": \"142\",\n",
|
||||||
|
" \"id\": \"0182b4d895b1c467290ae7b5c6c42ff76b2a4225807a94211c973170d5a883eb\",\n",
|
||||||
|
" \"outpoint\": \"7e6360d6e6a394cfd096adf0bfe1275c5a83541eb573e90e463a78dc715f8894:0\",\n",
|
||||||
|
" \"state\": \"WITHDRAWN\",\n",
|
||||||
|
" \"swap_hash\": \"\",\n",
|
||||||
|
" \"value\": \"500000\"\n",
|
||||||
|
" }\n",
|
||||||
|
" ]\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 271,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
62
cmd/loop/testdata/sessions/static-filters/04_loop-static-listdeposits-looping_in.json
vendored
Normal file
62
cmd/loop/testdata/sessions/static-filters/04_loop-static-listdeposits-looping_in.json
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"static",
|
||||||
|
"listdeposits",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--filter",
|
||||||
|
"looping_in"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 268850770,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsRequest",
|
||||||
|
"payload": {
|
||||||
|
"state_filter": "LOOPING_IN",
|
||||||
|
"outpoints": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 268,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsResponse",
|
||||||
|
"payload": {
|
||||||
|
"filtered_deposits": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 268,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"filtered_deposits\": []\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 268,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
82
cmd/loop/testdata/sessions/static-filters/05_loop-static-listdeposits-looped_in.json
vendored
Normal file
82
cmd/loop/testdata/sessions/static-filters/05_loop-static-listdeposits-looped_in.json
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"static",
|
||||||
|
"listdeposits",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--filter",
|
||||||
|
"looped_in"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 169458759,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 3,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsRequest",
|
||||||
|
"payload": {
|
||||||
|
"state_filter": "LOOPED_IN",
|
||||||
|
"outpoints": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 168,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsResponse",
|
||||||
|
"payload": {
|
||||||
|
"filtered_deposits": [
|
||||||
|
{
|
||||||
|
"id": "j71tovlF3ikFqn+pOGB0TZOH00ZEhDYOluRnpR3jvJ0=",
|
||||||
|
"state": "LOOPED_IN",
|
||||||
|
"outpoint": "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0",
|
||||||
|
"value": "500000",
|
||||||
|
"confirmation_height": "148",
|
||||||
|
"blocks_until_expiry": "14383",
|
||||||
|
"swap_hash": "hDAjN0JANkGTlqt5ZN14uFsaSBqfHbc9tc3e5XwkQ+c="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 168,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"filtered_deposits\": [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"blocks_until_expiry\": \"14383\",\n",
|
||||||
|
" \"confirmation_height\": \"148\",\n",
|
||||||
|
" \"id\": \"8fbd6da2f945de2905aa7fa93860744d9387d3464484360e96e467a51de3bc9d\",\n",
|
||||||
|
" \"outpoint\": \"9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0\",\n",
|
||||||
|
" \"state\": \"LOOPED_IN\",\n",
|
||||||
|
" \"swap_hash\": \"84302337424036419396ab7964dd78b85b1a481a9f1db73db5cddee57c2443e7\",\n",
|
||||||
|
" \"value\": \"500000\"\n",
|
||||||
|
" }\n",
|
||||||
|
" ]\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 169,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"static",
|
||||||
|
"listdeposits",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--filter",
|
||||||
|
"publish_expired_deposit"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 230057326,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 2,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsRequest",
|
||||||
|
"payload": {
|
||||||
|
"state_filter": "PUBLISH_EXPIRED",
|
||||||
|
"outpoints": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 229,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsResponse",
|
||||||
|
"payload": {
|
||||||
|
"filtered_deposits": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 230,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"filtered_deposits\": []\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 230,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
62
cmd/loop/testdata/sessions/static-filters/07_loop-static-listdeposits-sweep_htlc_timeout.json
vendored
Normal file
62
cmd/loop/testdata/sessions/static-filters/07_loop-static-listdeposits-sweep_htlc_timeout.json
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"args": [
|
||||||
|
"/home/user/bin/loop",
|
||||||
|
"static",
|
||||||
|
"listdeposits",
|
||||||
|
"--network",
|
||||||
|
"regtest",
|
||||||
|
"--filter",
|
||||||
|
"sweep_htlc_timeout"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"HOME": "/home/user"
|
||||||
|
},
|
||||||
|
"version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845",
|
||||||
|
"duration": 224559305,
|
||||||
|
"clock_start_unix": 1769407086
|
||||||
|
},
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"time_ms": 1,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "request",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsRequest",
|
||||||
|
"payload": {
|
||||||
|
"state_filter": "SWEEP_HTLC_TIMEOUT",
|
||||||
|
"outpoints": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 223,
|
||||||
|
"kind": "grpc",
|
||||||
|
"data": {
|
||||||
|
"method": "/looprpc.SwapClient/ListStaticAddressDeposits",
|
||||||
|
"event": "response",
|
||||||
|
"message_type": "looprpc.ListStaticAddressDepositsResponse",
|
||||||
|
"payload": {
|
||||||
|
"filtered_deposits": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 224,
|
||||||
|
"kind": "stdout",
|
||||||
|
"data": {
|
||||||
|
"lines": [
|
||||||
|
"{\n",
|
||||||
|
" \"filtered_deposits\": []\n",
|
||||||
|
"}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time_ms": 224,
|
||||||
|
"kind": "exit",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue