Skip to content

feat(withdraw): Binance travel-rule (localentity) withdrawal support - #51

Merged
outerlook merged 2 commits into
developfrom
feat/binance-travel-rule-withdraw
Jul 1, 2026
Merged

feat(withdraw): Binance travel-rule (localentity) withdrawal support#51
outerlook merged 2 commits into
developfrom
feat/binance-travel-rule-withdraw

Conversation

@outerlook

@outerlook outerlook commented Jul 1, 2026

Copy link
Copy Markdown
Member

Problem

Binance rejects the standard POST /sapi/v1/capital/withdraw/apply endpoint with error -4104 ("withdrawals are not permitted due to travel rule restrictions") for accounts in jurisdictions that require travel-rule metadata — e.g. Australia under AUSTRAC from 2026-07-01. Once an account's jurisdiction requires it, the standard endpoint stops accepting withdrawals entirely; Binance only accepts POST /sapi/v1/localentity/withdraw/apply, which carries an extra required questionnaire field (a JSON string of beneficiary answers).

ccxt has no wrapper for the localentity endpoint.

Change

Opt-in support for the travel-rule withdraw endpoint:

  • Runtime endpoint registration (travel-rule.ts): registers localentity/withdraw/apply on the Binance instance via ccxt's own defineRestApi. No ccxt fork change — binance's sign() already signs all sapi private POSTs, and the path falls into the urlencode branch (which URL-encodes the questionnaire JSON, as the spec requires).
  • Policy schema (types.ts, index.ts): new optional travelRule section — a per-exchange enabled flag plus static questionnaire answers keyed by destination address. The Australia questionnaire is validated at policy-load time (australiaQuestionnaireSchema), so a malformed questionnaire fails startup, not a live withdrawal. Includes an optional description field so operators can document why an entry exists.
  • Dispatch (withdraw.ts): when travel rule is enabled for the exchange, resolve the questionnaire by destination address (case-insensitive) and route to the localentity endpoint. Fails closed — if enabled but no questionnaire is configured for the address, returns FAILED_PRECONDITION rather than falling back to the standard endpoint (which would just reproduce -4104).

Non-Binance exchanges and accounts with enabled: false keep using the standard endpoint unchanged.

Tests

New test/travel-rule.test.ts (19 tests): AU questionnaire validation (self-owned, individual, corporate, VASP branches + conditional-required/forbidden rules), decision resolution (standard / localentity / fail-closed), endpoint registration (Binance vs no-op), the localentity request shape (incl. questionnaire JSON serialization), and loadPolicy acceptance/rejection.

Full suite: 291 pass / 0 fail. tsc and biome clean.

Notes

  • Version bumped to 0.2.15. Consumers (fiet-maker) must upgrade to this before adding a travelRule section, since older loadPolicy rejects the unknown key.
  • Only the Australia questionnaire validator is implemented for now (YAGNI); other jurisdictions can be added later.

Summary by CodeRabbit

  • New Features

    • Added policy-driven travel-rule configuration for withdrawals, including per-destination Australia questionnaire data for eligible Binance transfers.
    • Registered a Binance “localentity/withdraw/apply” route and added automatic routing to it when enabled and a matching questionnaire is available.
  • Bug Fixes

    • Withdrawals now stop early with a clear failure when travel-rule mode is denied or when a required destination questionnaire is missing.
  • Tests

    • Added end-to-end coverage for policy validation, decision resolution, and localentity withdrawal behavior.
  • Chores

    • Bumped the package version to 0.2.15.

Binance rejects the standard capital/withdraw/apply endpoint with error
-4104 for jurisdictions that require travel-rule metadata (e.g. Australia
under AUSTRAC from 2026-07-01). Add opt-in support for the
localentity/withdraw/apply endpoint, which carries a per-address
questionnaire of beneficiary answers.

- Register the localentity endpoint at runtime via ccxt defineRestApi
  (no ccxt fork change; binance sign() already signs sapi POSTs)
- Add a per-exchange travelRule policy section (enabled flag + static
  questionnaire keyed by destination address), validated against the
  Australia questionnaire schema at policy-load time
- Route withdrawals to the localentity endpoint when enabled; fail closed
  when enabled but no questionnaire is configured for the address

Bumps version to 0.2.15.
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a6b5c5b7-b5e4-403a-a2e3-de850c8131f3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR adds Binance travel-rule support for withdrawals: new types and validation, policy loading for travel-rule rules, endpoint registration, withdrawal routing logic, a policy example, tests, and a version bump.

Changes

Travel-Rule Withdrawal Feature

Layer / File(s) Summary
Travel-rule types and PolicyConfig extension
src/types.ts
Adds TravelRuleQuestionnaire, TravelRuleAddressEntry, TravelRuleEntry types and extends PolicyConfig with an optional travelRule section.
Travel-rule validation schema, decision resolver, and Binance endpoint/withdraw helpers
src/helpers/travel-rule.ts
Implements australiaQuestionnaireSchema, TravelRuleDecision, resolveTravelRuleDecision, registerBinanceTravelRuleWithdrawEndpoint, and withdrawViaLocalEntity.
Policy schema wiring and exchange endpoint registration
src/helpers/index.ts, src/helpers/broker.ts
Wires travel-rule schema into policy validation, re-exports travel-rule symbols, and registers Binance's travel-rule withdraw endpoint during common exchange configuration.
Withdraw handler integration
src/handlers/execute-action/withdraw.ts
Resolves travel-rule decisions after policy validation, denies withdrawals with FAILED_PRECONDITION when disallowed, and routes to withdrawViaLocalEntity or broker.withdraw.
Policy example and version bump
policy/policy.example.json, package.json
Adds a BINANCE travelRule example entry and bumps the package version to 0.2.15.
Travel-rule test suite
test/travel-rule.test.ts
Adds tests for questionnaire validation, decision resolution, endpoint registration, local-entity withdrawal, and loadPolicy validation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant WithdrawHandler
  participant resolveTravelRuleDecision
  participant BinanceExchange

  Client->>WithdrawHandler: withdraw request
  WithdrawHandler->>resolveTravelRuleDecision: resolveTravelRuleDecision(policy, exchange, address)
  resolveTravelRuleDecision-->>WithdrawHandler: standard | localentity | denied
  alt denied
    WithdrawHandler-->>Client: FAILED_PRECONDITION travel_rule_denied
  else localentity
    WithdrawHandler->>BinanceExchange: withdrawViaLocalEntity(...)
    BinanceExchange-->>WithdrawHandler: transaction response
    WithdrawHandler-->>Client: withdrawal response
  else standard
    WithdrawHandler->>BinanceExchange: broker.withdraw(...)
    BinanceExchange-->>WithdrawHandler: transaction response
    WithdrawHandler-->>Client: withdrawal response
  end
Loading

Poem

A rabbit hops with careful pace,
Through Binance gates and rules to trace.
Questionnaires bloom, the paths align,
Standard or localentity, all just fine.
Hop hop — the ledger keeps its tune 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: Binance localentity travel-rule support for withdrawals.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/binance-travel-rule-withdraw

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/handlers/execute-action/withdraw.ts`:
- Around line 105-111: The local-entity path in withdraw logic is dropping
exchange-specific withdrawal params while the standard path preserves them. In
the `withdraw` handler, either validate and reject non-empty
`transferValue.params` before calling `withdrawViaLocalEntity`, or update
`withdrawViaLocalEntity` to accept and forward the supported params consistently
with the other withdrawal branch. Use the `withdraw`, `withdrawViaLocalEntity`,
and `transferValue.params` symbols to locate the affected flow.

In `@src/helpers/index.ts`:
- Around line 85-97: The travel-rule entry schema in travelRuleEntrySchema
currently allows any exchange value, which lets non-Binance entries reach
withdrawViaLocalEntity and fail later. Tighten the validation in
src/helpers/index.ts so exchange is restricted to BINANCE only, or add an
equivalent guard in the resolver path that uses travel-rule entries before
routing. Use the existing travelRuleEntrySchema and withdrawViaLocalEntity
symbols to locate and update the validation flow.

In `@src/helpers/travel-rule.ts`:
- Around line 133-178: The localentity withdrawal path in withdrawViaLocalEntity
is dropping extra withdrawal parameters that handleWithdraw already forwards in
the standard path. Add a params field to LocalEntityWithdrawArgs and thread
transferValue.params from the withdraw action into withdrawViaLocalEntity, then
merge those params into the request built in withdrawViaLocalEntity so Binance
memo/tag or wallet-specific options are preserved alongside the existing
coin/address/amount/network/questionnaire fields.
- Around line 91-103: The exchange lookup in travelRule logic can be bypassed by
duplicate entries and can incorrectly apply Binance-only handling to non-Binance
exchanges. Update the resolver in the travel-rule helper to select an enabled
Binance rule only, and avoid falling back to standard or invoking the
localentity path for any non-Binance exchange. Use the existing travel-rule
resolution flow and identifiers like the exchange matching block and the
Binance-only helper entry point to tighten the selection logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: baaff3e6-f656-413f-b955-38549161b353

📥 Commits

Reviewing files that changed from the base of the PR and between 79916e3 and 470fa70.

📒 Files selected for processing (8)
  • package.json
  • policy/policy.example.json
  • src/handlers/execute-action/withdraw.ts
  • src/helpers/broker.ts
  • src/helpers/index.ts
  • src/helpers/travel-rule.ts
  • src/types.ts
  • test/travel-rule.test.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-04-14T06:47:01.283Z
Learnt from: csmithington
Repo: usherlabs/cex-broker PR: 38
File: src/client.dev.ts:0-0
Timestamp: 2026-04-14T06:47:01.283Z
Learning: In this codebase, gRPC action constants such as `FetchTicker`, `FetchFees`, and `FetchAccountId` should be sourced from `src/helpers/constants.ts` and imported from there (e.g., used by both `src/client.dev.ts` and `src/server.ts`). Do not import or reference generated proto TypeScript artifacts (for example `./proto/cex_broker/Action`), since those generated files are git-ignored and won’t be available/committed consistently.

Applied to files:

  • src/helpers/broker.ts
  • src/handlers/execute-action/withdraw.ts
  • src/types.ts
  • src/helpers/index.ts
  • src/helpers/travel-rule.ts
🪛 ast-grep (0.44.0)
test/travel-rule.test.ts

[warning] 261-261: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(tempPath, JSON.stringify(policy))
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🔇 Additional comments (8)
package.json (1)

3-3: LGTM!

test/travel-rule.test.ts (2)

258-264: False positive: static analysis path-traversal flag.

tempPath is derived from os.tmpdir(), Date.now(), and Math.random() — not attacker-controlled input — so the CWE-22 flag on fs.writeFileSync doesn't apply here.


1-297: LGTM!

policy/policy.example.json (1)

47-64: LGTM!

src/types.ts (1)

29-76: LGTM!

src/helpers/index.ts (1)

17-17: LGTM!

Also applies to: 33-39

src/helpers/broker.ts (1)

7-7: LGTM!

Also applies to: 55-56

src/handlers/execute-action/withdraw.ts (1)

2-6: LGTM!

Also applies to: 87-100

Comment thread src/handlers/execute-action/withdraw.ts
Comment thread src/helpers/index.ts
Comment thread src/helpers/travel-rule.ts
Comment thread src/helpers/travel-rule.ts
Addresses review feedback on #51:

- travelRuleEntrySchema now only accepts BINANCE (the sole exchange with a
  localentity endpoint), so a misconfigured non-Binance entry fails at policy
  load instead of throwing a cryptic "endpoint not registered" error on the
  first withdrawal.
- withdrawViaLocalEntity now forwards caller params (memo/tag, withdrawOrderId,
  etc.), matching the standard withdraw path. Fixed fields
  (coin/amount/network/questionnaire) still take precedence on collision.
@outerlook
outerlook merged commit 5b8091a into develop Jul 1, 2026
5 checks passed
@outerlook

Copy link
Copy Markdown
Member Author

@coderabbitai not chasing the docstring-coverage threshold on this PR. The exported functions (resolveTravelRuleDecision, registerBinanceTravelRuleWithdrawEndpoint, withdrawViaLocalEntity) and the questionnaire schema each carry doc comments covering intent and the travel-rule background; the sub-80% figure is driven by small internal helpers and type declarations where a docstring would only restate the signature.

The 4 actionable inline findings (Binance-only travelRule restriction + forwarding withdraw params) are addressed in 54be075.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

@outerlook, that's a reasonable rationale — docstrings that would only restate a type signature don't add value, and the core exported functions/schema already have the intent and travel-rule context documented. I won't press further on the coverage percentage for this PR.

Thanks for confirming the fixes in 54be075 for the Binance-only travelRule restriction and the withdraw param forwarding — I'll take a look at that commit in the next review pass.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

outerlook added a commit that referenced this pull request Jul 2, 2026
Resolve conflicts combining the ClickHouse research/archive stack (this PR)
with the travel-rule deposit reconciler landed on develop (#51-#54):
- src/index.ts: keep both CEXBroker fields (brokerArchiver + depositReconciler);
  both are referenced by the auto-merged body.
- .env.sample: keep develop's OTel legacy host/port + travel-rule docs (env vars
  still consumed by src/helpers/otel.ts) and append this PR's ClickHouse block.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant