feat(v2): crypto 孤儿到账发现——OrphanScanner 扫链核对 + orphan_payments 落表告警(对账兜底)
This commit is contained in:
@@ -240,6 +240,69 @@ func (p *Provider) VerifyCallback(_ context.Context, _ provider.CallbackInput) (
|
||||
return nil, provider.ErrNotSupported
|
||||
}
|
||||
|
||||
// ScanOrphans 扫地址近 Since 的确认到账,金额不在"任一 Known 的期望金额集"内 → 孤儿。
|
||||
// 期望金额 = known.AmountMinor + tailFromRef(known.ProviderRef);块时须晚于 Since。
|
||||
func (p *Provider) ScanOrphans(ctx context.Context, req provider.OrphanScanRequest) ([]provider.OrphanTransfer, error) {
|
||||
addr, err := p.address(req.AccountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expected := make(map[int64]struct{}, len(req.Known))
|
||||
for _, k := range req.Known {
|
||||
tail, terr := tailFromRef(k.ProviderRef)
|
||||
if terr != nil {
|
||||
continue // 无尾数的 ref 跳过(不误判为孤儿依据)
|
||||
}
|
||||
expected[k.AmountMinor+tail] = struct{}{}
|
||||
}
|
||||
|
||||
endpoint := fmt.Sprintf("%s/v1/accounts/%s/transactions/trc20?only_confirmed=true&contract_address=%s&limit=50",
|
||||
p.baseURL, url.PathEscape(addr), url.QueryEscape(USDTContract))
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if k := p.apiKey(req.AccountID); k != "" {
|
||||
httpReq.Header.Set("TRON-PRO-API-KEY", k)
|
||||
}
|
||||
resp, err := p.http.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("crypto: TronGrid HTTP %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
var tr trc20Resp
|
||||
if err := json.Unmarshal(body, &tr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sinceUnix := req.Since.Unix()
|
||||
var out []provider.OrphanTransfer
|
||||
for _, d := range tr.Data {
|
||||
if d.To != addr || d.Type != "Transfer" {
|
||||
continue
|
||||
}
|
||||
blockTs := d.BlockMs / 1000
|
||||
if blockTs < sinceUnix {
|
||||
continue // 窗外旧款不扫(避免把历史正常单反复报孤儿)
|
||||
}
|
||||
val, perr := strconv.ParseInt(d.Value, 10, 64)
|
||||
if perr != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := expected[val]; ok {
|
||||
continue // 金额有主(匹配某 attempt 期望额)→ 非孤儿
|
||||
}
|
||||
out = append(out, provider.OrphanTransfer{
|
||||
TxID: d.TxID, AmountMinor: val, Currency: "USDT", At: time.Unix(blockTs, 0),
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// trc20Resp 对应 TronGrid /v1/accounts/{addr}/transactions/trc20 响应
|
||||
// (canonical tron/client.go 同构;contract_address 查询参数已在服务端过滤合约)。
|
||||
type trc20Resp struct {
|
||||
|
||||
Reference in New Issue
Block a user