Skip to content

feat(blockchain): cross-chain ADR-015 deposit reference#4

Merged
philanton merged 5 commits into
masterfrom
feat/deposit-destination
Jun 19, 2026
Merged

feat(blockchain): cross-chain ADR-015 deposit reference#4
philanton merged 5 commits into
masterfrom
feat/deposit-destination

Conversation

@philanton

Copy link
Copy Markdown
Contributor

Adds an opaque sub-account reference to the deposit API across all chains, building on the EVM binding from #2.

API

VaultDepositor.SubmitDeposit now takes a core.DepositDestination:

type DepositDestination struct {
    Account string   // L1/clearnet account credited
    Ref     [32]byte // ADR-015 opaque sub-account reference; zero = none
}

The reference is side-data — never interpreted on-chain — so deposits are filterable per (Account, Ref). An observer folds it into the account URI; the submit side just carries it.

Per-chain

  • EVM — passed to deposit(account, asset, amount, reference).
  • SOL — Solana program artifact (IDL + .so) + binding refreshed to the reference-carrying deposit_sol/deposit_spl(account, reference, amount); depositor forwards Ref.
  • XRPL — switched from DestinationTag to a ynet-account memo (20-byte account || 32-byte reference), matching the observe-side decoder. New accountMemo + unit test.
  • BTC — a plain send has no side-data channel (the account is the per-account deposit address), so a non-zero Ref is rejected.

Scope / breaking

SubmitDeposit's signature changes (pre-1.0). VerifyDeposit unchanged. Tag as v0.1.2 after merge.

Verification

go generate, go build/vet (incl. -tags=integration), go test -count=1 ./... — all green. SOL program id unchanged (98eVpih…); integration deposit flows updated to DepositDestination.

🤖 Generated with Claude Code

philanton and others added 5 commits June 18, 2026 17:56
SubmitDeposit now takes a DepositDestination{Account, Ref}: the opaque
ADR-015 sub-account reference travels alongside the account as side-data,
never interpreted on-chain, so deposits are filterable per (account, ref).

- EVM / SOL emit it in the deposit call / instruction; the Solana program
  artifact + binding are refreshed to the reference-carrying
  deposit_sol/deposit_spl.
- XRPL moves from a DestinationTag to a ynet-account memo (20-byte account
  followed by the 32-byte reference), matching the observe-side decoder.
- BTC has no side-data channel on a plain send — the account is encoded in
  the per-account deposit address — so a non-zero reference is rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RotationFinalizer.Validate checked that every input present in the
packed sweep was a valid, confirmed vault UTXO, but never checked that
the sweep spent ALL currently-owned UTXOs. A sweep that silently omitted
some inputs would still pass, and once the rotation pivot lands the old
vault is abandoned — any unswept UTXO is stranded there with no path to
move it under the new signer set.

Add a completeness check: re-list the owned UTXO set at the configured
confirmation depth and require exact set-equality with the tx inputs,
erroring on a count mismatch or any omitted owned UTXO. Reuses the
current-vault finalizer already built for input summation.

Adds TestRotationValidateRejectsPartialSweep: a full sweep validates, a
sweep with one input dropped is rejected naming the omitted UTXO.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RotationFinalizer.Submit relied on the contract binding's implicit gas
auto-estimation for the updateSigners call. updateSigners takes
dynamic-array arguments (address[], uint256, bytes[]), and the binding's
built-in estimate trips a known go-ethereum gotcha on dynamic args that
returns a too-low gas limit (or an estimation error outright), so the
rotation transaction can revert out-of-gas — a liveness failure on the
signer-rotation path.

Estimate gas explicitly, mirroring the withdrawal path: pack the
updateSigners calldata, run a single eth_estimateGas against it, and set
opts.GasLimit to the result padded by the configured multiplier. Called
after applyFees and before the binding call. A comment documents the
dynamic-array gotcha so the explicit estimate is not removed later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SelectUTXOs gains a maxInputs bound (Config.MaxInputsPerWithdrawal); when
covering a withdrawal would exceed it, the vault is too fragmented for a
standard-size tx, signalled by the new ErrTooFragmented.

ConsolidationFinalizer folds a bounded batch of the vault's smallest
UTXOs back into one base-vault output (Pack/Validate/Sign/Submit),
shrinking the count so withdrawals keep fitting. It is partial by design
(same vault, no pivot), so unlike the rotation sweep it carries no
completeness rule; Sign/Submit reuse the current-vault machinery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pack autofills the multi-sign fee from the construction-time threshold,
so a quorum-raising rotation underpays once the SignerQuorum grows. Add
an optional ThresholdResolver hook (SetThresholdResolver) the withdrawal
and rotation finalizers consult for the live SignerQuorum; unset keeps
the static threshold.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@philanton philanton merged commit df7d8b9 into master Jun 19, 2026
2 checks passed
@philanton philanton deleted the feat/deposit-destination branch June 19, 2026 10:43
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