Foreshock
EN 订阅
文档

接入

接入贷记闸门

每笔入金在贷记之前调用一次,带一个稳定的幂等键,并为每一种结果准备好分支。

更新于
2026-08-24

先读 fail-closed 客户端契约。这一页是顺路径;那一页是顺路径不可用时保住你的那一页。

这次调用放在哪

放在「一笔入金在链上确认」与「用户余额发生变化」之间。不是之后。之后做的任何事都不是控制措施——回滚是道歉,不是闸门。

1 · 构造请求

{
  "deposit_ref": "deposit-EXAMPLE-0001",
  "chain": "example-chain",
  "tx_hash": "0xEXAMPLEtxhash000000000000000000000000000000000000000000000000000",
  "from_address": "0xEXAMPLEaddress0000000000000000000000001",
  "to_address": "0xEXAMPLEaddress0000000000000000000000002",
  "asset": "EXAMPLEUSD",
  "amount_usd": "25000.00",
  "user_ref": "user-EXAMPLE-0001",
  "kyc_ref": "kyc-EXAMPLE-0001"
}

上面每一个值都是合成占位符,在任何链上都不成立。

amount_usd 刻意是十进制字符串,以免浮点丢精度。user_ref 与 kyc_ref 是你自己的标识符;Foreshock 不做也不评估 KYC,只把它们当引用存下来。

在冻结原语作用于 token account 而非钱包的链上,状态按 token account 计。同一钱包的两个 token account 可以处于不同状态,必须分别提交。

2 · 带一个稳定的幂等键发出

POST /v1/decide/deposit HTTP/1.1
Host: api.foreshock.org
Authorization: Bearer fsk_EXAMPLE_0000000000000000
Idempotency-Key: 22222222-2222-4222-8222-222222222222
Content-Type: application/json

键从 deposit_ref 派生,不要从尝试次数派生,这样这笔入金的每次重试都收敛到同一条已存储的裁决。

3 · 读裁决

{
  "decision_id": "11111111-1111-4111-8111-111111111111",
  "verdict": "HOLD",
  "risk_level": "R1",
  "pool": "quarantine",
  "reasons": [
    { "code": "ACTIVE_ENFORCEMENT_RELATION", "severity": "R1" },
    {
      "code": "VENDOR_HIGH_RISK",
      "vendor": "example-vendor",
      "raw_ref": "cas:sha256:EXAMPLE00000000000000000000000000000000000000000000000000000003"
    }
  ],
  "vendor_disagreement": false,
  "rule_version": "rules-EXAMPLE-2026.08.01",
  "required_actions": ["COLLECT_SOURCE_OF_FUNDS", "L2_REVIEW"],
  "sla_deadline": "2026-08-24T13:05:00Z",
  "evidence_ref": "cas:sha256:EXAMPLE00000000000000000000000000000000000000000000000000000004",
  "billable_units": 1
}

4 · 分支

verdict你的系统做什么
CREDIT贷记。把 decision_id 存在账本条目旁边。
HOLD不贷记。按 pool 路由资金。按 required_actions 开出待办,以 sla_deadline 计时。
REJECT不贷记。按 pool 路由。开案。

然后,无论裁决是什么,检查 Foreshock-Coverage-Health 响应头。为 degraded 时,转 GET /v1/coverage 取明细,并记录这条裁决是在可见性下降的情况下做出的。

5 · 路由到资金池,并且让池子彼此隔开

pool 取值 clean、tagged、quarantine。隔离本身就是全部意义所在:一个被扫进和其他资金同一个归集地址的 quarantine 分类,什么都没做到。required_actions 里的 QUARANTINE_ROUTE 是显式指令,但 pool 字段在每条裁决上都带着它。

6 · 完成待办动作,然后重新裁决

required_actions 是你的工作流要完成的步骤,不是我们执行的步骤。当挂起的原因是降级时,待办动作是 RE_DECIDE_AFTER_RECOVERY——降级恢复后再调一次这个接口。

那一次要用一个新的幂等键。你是在用新的输入问一个新问题;复用旧键会把陈旧的答案重放回来。

7 · 留住审计链

decision_id 用于从 GET /v1/decisions/{decision_id}/evidence 取证据包。rule_version 记录是你规则集的哪个版本产生了这个结果,因此这条裁决可以按当时生效的规则重放。

两者都存在账本条目旁边。裁决记录与快照存储,是这道闸门可审计而不只是「存在」的原因。

先跑影子模式

先上调用,再上执行:把每笔入金都过一遍闸门,记录裁决,照旧贷记,然后比对。跑一周,你就知道自己的挂起率、降级率,以及运营团队能不能吃下这个队列——在一个错误假设变成一个卡住的用户余额之前。