Files
zeekay c96474d7ff feat(bridge): wire TON end-to-end through mchain + pool + signing driver
internal/mchain:

- pickAddress for AddressTypeTON now derives the real v4r2 address
  from EDDSAPubKey when the keygen surfaces it (workchain=0 +
  DefaultSubwallet=698983191 + V4R2 code + pubkey → state_init.Hash).
  Falls back to the legacy SOL slot when the cluster hasn't yet
  emitted the Ed25519 pubkey, so existing mpcd deployments don't
  break.
- mchain.Wallet now carries ECDSAPubKey + EDDSAPubKey so downstream
  consumers (release_pool, signing_driver) can drive chain-specific
  flows without re-querying mpcd.
- New ton.go houses deriveTONAddressFromHex, delegating cell encoding
  to tonutils-go (same lib the txassembler depends on) so addresses
  surfaced at keygen time are bit-identical to those the txassembler
  rebuilds in state_init at first send.

cmd/bridge/release_pool.go:

- ReleasePoolEntry gains AddressType + ECDSAPubKey + EDDSAPubKey
  fields so the signing driver can route per-family. Legacy entries
  without these fields fall through to the EVM path (AddressType=
  empty equals "eth" via the dispatch default).

cmd/bridge/balance_probe.go:

- TON branch added to BalanceAt: GET <toncenter>/getAddressBalance,
  optional X-API-Key header from c.TONAPIKeys[network]. Returns
  *big.Int of nanoton. Same best-effort policy as the EVM path
  (probe failures log and skip rather than block signing).
- TONBalanceURLs + TONAPIKeys maps populated by main.go from
  networks.yaml's per-network Broadcast block.

cmd/bridge/signing_driver.go:

- New tonAssembler field + SetTONAssembler setter mirroring
  SetAssembler. Dispatch in signOne now branches on
  mchain.AddressTypeFor(DestinationNetwork): TON → PreSignTON path
  (looks up the pool entry's EDDSAPubKey, builds the v4r2 unsigned
  body, computes the nanoton-unit gas pre-check, finalizes via
  FinalizeTONHex into base64 BOC); otherwise → EVM path (unchanged).
- gasPrecheckTON: 0.05 TON gas floor for native transfers, 0.15 TON
  for jetton transfers (covers body value + forward TON + master
  call gas). Native transfers count the user amount against the
  release wallet; jetton transfers don't (the user value moves
  between jetton wallets, not out of ours).
- tonAmountToNano + isTONNativeAsset helpers — same fixed-point
  float→big.Int approach as the EVM floatToWei to avoid IEEE noise.

cmd/bridge/config.go:

- Network gains an optional Broadcast block (URL + APIKey) so
  operators configure TON Center endpoints per-network in
  networks.yaml without flag plumbing.

cmd/bridge/main.go:

- TON assembler instantiated at startup and wired into the signing
  driver via SetTONAssembler.
- Broadcast client picks up per-network sendBoc URLs + X-API-Keys
  from the new networks.yaml block.
- Balance probe picks up per-network getAddressBalance URLs (derived
  from sendBoc by path-swap) + the same X-API-Keys.
2026-05-27 18:04:24 -07:00

62 lines
2.4 KiB
Go

package mchain
import (
"crypto/ed25519"
"encoding/hex"
"fmt"
"strings"
tonwallet "github.com/xssnick/tonutils-go/ton/wallet"
)
// ton.go: TON address derivation from an MPC-supplied Ed25519 pubkey.
//
// This file is the bridge-side mirror of the cluster's eventual TON
// keygen output. The standard TON v4r2 wallet address is:
//
// workchain = 0
// state_init = StateInit { code: V4R2_CODE, data: { seqno=0, subwallet=698983191, pubkey, plugins=null } }
// addr_data = sha256(StateInit_cell)
// raw_form = "0:" + hex(addr_data)
//
// We delegate the actual cell encoding + hash to tonutils-go (the
// canonical Go SDK that the txassembler also depends on) so the
// address we surface from a keygen is bit-identical to the address
// the txassembler builds the state_init for in PreSignTON.
//
// Why mchain owns this rather than txassembler:
// - Address selection happens at keygen time, before any tx
// assembler is involved (the bridge stores the address and shows
// it to the SDK + UI before the user ever deposits).
// - mchain is the canonical "given a keygen result, what's the
// receive address" function — putting this with the other
// pickAddress branches keeps that contract in one place.
// deriveTONAddressFromHex parses a hex-encoded Ed25519 public key
// (32 bytes) and returns the v4r2 raw address ("0:<hex>") for that
// pubkey. Returns an error on malformed hex or wrong length.
//
// Hex input may carry a 0x prefix (case-insensitive). Trimmed and
// decoded with encoding/hex.
//
// The "raw form" return is intentional — it's the lowest-common-
// denominator representation. Callers that want the user-friendly
// "EQ..." / "UQ..." form should pull the txassembler helpers
// (TONAddressFromPubKey + .String()).
func deriveTONAddressFromHex(pubkeyHex string) (string, error) {
pubkeyHex = strings.TrimPrefix(strings.TrimPrefix(pubkeyHex, "0x"), "0X")
pubkey, err := hex.DecodeString(pubkeyHex)
if err != nil {
return "", fmt.Errorf("mchain: ton: decode ed25519 pubkey hex: %w", err)
}
if len(pubkey) != ed25519.PublicKeySize {
return "", fmt.Errorf("mchain: ton: ed25519 pubkey wrong length: got %d want %d",
len(pubkey), ed25519.PublicKeySize)
}
addr, err := tonwallet.AddressFromPubKey(pubkey, tonwallet.V4R2, tonwallet.DefaultSubwallet)
if err != nil {
return "", fmt.Errorf("mchain: ton: derive v4r2 address: %w", err)
}
return addr.StringRaw(), nil
}