Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5d01857
feat: integrate api-keys spending limits into payment flows and dashb…
bas4r Mar 2, 2026
8e1b253
test: add hold invoice reversal bats tests
bas4r Mar 2, 2026
a25aac5
fix: align part2 with part1 schema naming and generic limit mutations
bas4r Mar 9, 2026
c6ae38a
refactor: introduce ApiKeysServiceError base class and fix error levels
bas4r Mar 17, 2026
9014c68
refactor: simplify spending limit validation and include period in er…
bas4r Mar 17, 2026
439d542
fix: make recordSpending fire-and-forget and simplify validation checks
bas4r Mar 17, 2026
96a801c
fix: move ApiKeysService instantiation to module scope in update-pend…
bas4r Mar 17, 2026
e3e4c5f
feat: add tracing to ApiKeysService via wrapAsyncFunctionsToRunInSpan
bas4r Mar 17, 2026
86b97bb
fix: reject zero amount in check_spending_limit for consistency with …
bas4r Mar 17, 2026
7a33c53
fix: add ApiKeysServiceError to graphql error map
bas4r Mar 17, 2026
7fc8635
chore: remove unnecessary comments from dashboard api-keys code
bas4r Mar 17, 2026
8bc4a63
refactor: use proper domain types in api-keys spending limits interface
bas4r Mar 18, 2026
a73903f
feat: Api keys usage limit part2 atomic check and lock
bas4r Mar 23, 2026
367e452
chore: fix lint and formatting
bas4r Mar 23, 2026
c89e097
fix(payments): centralize spending limit settlement and cancellation …
dolcalmi Apr 8, 2026
fa630bc
refactor(api-keys): add tracing to check_spending_limit and remove re…
dolcalmi Apr 8, 2026
aea930b
chore: revert apps/dashboard changes (kept in separate branch)
dolcalmi Apr 8, 2026
ab81c42
chore: remove new dashboard limit.tsx (kept in separate branch)
dolcalmi Apr 8, 2026
2184256
fix: correct Writeable typo to Writable
dolcalmi Apr 8, 2026
8de200d
fix: bats lint issues
dolcalmi Apr 8, 2026
316fced
test(api-keys): add spending limits and hold invoice reversal BATS tests
dolcalmi Apr 8, 2026
12ea22c
fix(api-keys): guard against NULL transaction_id in record_spending
dolcalmi Apr 8, 2026
95f1a65
fix(api-keys): harden spending finalization and consistency checks
dolcalmi Apr 9, 2026
60e8834
fix(core-api): map api-keys spending finalize errors
dolcalmi Apr 9, 2026
aeacbe8
fix(core-api): explicit spending settlement intent
dolcalmi Apr 9, 2026
3f4b3c1
fix(api-keys): make ephemeral finalization conflict-safe and improve …
dolcalmi Apr 9, 2026
5dabf43
fix: solve audit issues related to axios
dolcalmi Apr 9, 2026
57550bf
fix: spending limit bypass, race condition, and BigInt truncation
dolcalmi Apr 9, 2026
d6f459e
fix(core-api): reverse ErrorWithJournal settlement and unify spending…
dolcalmi Apr 9, 2026
6ae17b5
fix(api-keys): reduce hashtext collision risk in advisory lock
dolcalmi Apr 10, 2026
6ca01a7
fix: lazy gRPC client instantiation
dolcalmi Apr 10, 2026
8720ea5
fix: rollback send tx notification update
dolcalmi Apr 10, 2026
e06e7eb
fix: remove unnecessary code
dolcalmi Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/consent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@opentelemetry/semantic-conventions": "1.27.0",
"@ory/hydra-client": "^2.2.0",
"@t3-oss/env-nextjs": "^0.7.1",
"axios": "^1.11.0",
"axios": "^1.15.0",
"dotenv": "^16.3.1",
"edge-csrf": "^1.0.6",
"graphql": "^16.8.1",
Expand Down
98 changes: 98 additions & 0 deletions bats/core/api-keys/api-keys-hold-invoice-reversal.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env bats

# Tests for API key spending reversal when hold invoices are canceled/timeout

load "../../helpers/user.bash"
load "../../helpers/onchain.bash"
load "../../helpers/ln.bash"

ALICE='alice'

setup_file() {
clear_cache

# Ensure LND has sufficient balance for lightning tests
lnd1_balance=$(lnd_cli channelbalance | jq -r '.balance // 0')
if [[ $lnd1_balance -lt "1000000" ]]; then
create_user 'lnd_funding'
fund_user_lightning 'lnd_funding' 'lnd_funding.btc_wallet_id' '5000000'
fi

create_user "$ALICE"
fund_user_onchain "$ALICE" 'btc_wallet'
}

@test "hold-invoice-reversal: create api key with daily limit" {
key_name="$(random_uuid)"
cache_value 'hold_invoice_key_name' "$key_name"

variables="{\"input\":{\"name\":\"${key_name}\",\"scopes\":[\"READ\",\"WRITE\"]}}"

exec_graphql 'alice' 'api-key-create' "$variables"
key="$(graphql_output '.data.apiKeyCreate.apiKey')"
secret="$(graphql_output '.data.apiKeyCreate.apiKeySecret')"

cache_value "api-key-hold-invoice-secret" "$secret"

name=$(echo "$key" | jq -r '.name')
[[ "${name}" = "${key_name}" ]] || exit 1

key_id=$(echo "$key" | jq -r '.id')
cache_value "hold-invoice-api-key-id" "$key_id"

variables="{\"input\":{\"id\":\"${key_id}\",\"limitTimeWindow\":\"DAILY\",\"limitSats\":10000}}"
exec_graphql 'alice' 'api-key-set-limit' "$variables"

daily_limit="$(graphql_output '.data.apiKeySetLimit.apiKey.limits.dailyLimitSats')"
[[ "${daily_limit}" = "10000" ]] || exit 1

spent_24h="$(graphql_output '.data.apiKeySetLimit.apiKey.limits.dailySpentSats')"
[[ "${spent_24h}" = "0" ]] || exit 1
}

@test "hold-invoice-reversal: pay hold invoice and check spending recorded" {
secret=$(xxd -l 32 -c 256 -p /dev/urandom)
payment_hash=$(echo -n $secret | xxd -r -p | sha256sum | cut -d ' ' -f1)

cache_value "hold-invoice-preimage" "$secret"
cache_value "hold-invoice-payment-hash" "$payment_hash"

invoice_response="$(lnd_outside_cli addholdinvoice "$payment_hash" --amt 5000 --memo 'Test hold invoice')"
payment_request="$(echo "$invoice_response" | jq -r '.payment_request')"

cache_value "hold-invoice-payment-request" "$payment_request"

variables=$(
jq -n \
--arg wallet_id "$(read_value $ALICE.btc_wallet_id)" \
--arg payment_request "$payment_request" \
'{input: {walletId: $wallet_id, paymentRequest: $payment_request}}'
)

exec_graphql 'api-key-hold-invoice-secret' 'ln-invoice-payment-send' "$variables"
send_status="$(graphql_output '.data.lnInvoicePaymentSend.status')"
[[ "${send_status}" = "PENDING" || "${send_status}" = "SUCCESS" ]] || exit 1

invoice_info="$(lnd_outside_cli lookupinvoice "$payment_hash")"
state="$(echo "$invoice_info" | jq -r '.state')"
[[ "${state}" = "ACCEPTED" ]] || exit 1

exec_graphql 'alice' 'api-keys'
spent_24h="$(graphql_output '.data.me.apiKeys[] | select(.name == "'$(read_value 'hold_invoice_key_name')'") | .limits.dailySpentSats')"
[[ "${spent_24h}" -ge "5000" ]] || exit 1
}

@test "hold-invoice-reversal: verify spending was reversed after cancellation" {
payment_hash="$(read_value 'hold-invoice-payment-hash')"

lnd_outside_cli cancelinvoice "$payment_hash"

check_spending_reversed() {
exec_graphql 'alice' 'api-keys'
spent_24h="$(graphql_output '.data.me.apiKeys[] | select(.name == "'$(read_value 'hold_invoice_key_name')'") | .limits.dailySpentSats')"
[[ "${spent_24h}" = "0" ]] || exit 1
}

# Poll until the trigger server processes the cancellation and reverses spending
retry 30 1 check_spending_reversed
}
Loading
Loading