Equillar如何确保投资合同的支付能力
Source: Dev.to
任何投资平台最关键的方面之一是确保投资者能够可靠且准时地收到付款。在 Equillar,我们实现了一个自动化系统,持续验证每个活跃投资合同的支付能力。在本文中,我们将探讨该机制在内部是如何运作的。
为什么验证支付能力很重要?
想象一下,你有一个活跃的投资合同,多个投资者正在等待他们的月度回报。合同可能运行正常,收到资金并处理操作,但如果在某个时点,储备金不足以覆盖下一笔付款,会发生什么?
为了尽可能避免这种情况,我们选择了以下做法:在问题发生之前进行检测,让组织能够及时采取纠正措施。
Source: …
验证流程:三阶段架构
我们的支付能力验证系统分为三个阶段,采用基于命令、异步消息和具体服务的架构。
第1阶段:识别待验证的合约
一切始于定期运行的计划命令:
php bin/console app:contract:check-payment-availability
该命令(CheckContractPaymentAvailabilityCommand)是验证系统的入口点。其逻辑简单且专注:
-
选择符合条件的合约 – 并非所有合约都需要持续验证。命令仅搜索处于运营状态的合约:
ACTIVE– 正在接受投资的活跃合约FUNDS_REACHED– 已达到融资目标的合约PAUSED– 暂停但仍有付款义务的合约
-
创建验证记录 – 对每个选中的合约,系统会创建一个
ContractPaymentAvailability实体。该记录充当验证“票据”,存储:- 待验证的合约
- 请求创建时间
- 处理状态(
pending、processed、failed) - 验证结果
-
将工作加入队列 – 为避免同步处理导致系统阻塞(可能需要数分钟),命令会通过 Symfony Messenger 将每个验证加入异步消息队列。
这种设计很重要,因为它让命令能够快速结束,而繁重的工作则在后台处理。如果有 50 个活跃合约,命令只会创建 50 张“票据”并入队,几秒钟内即可返回控制权。
第2阶段:异步处理
消息进入队列后,Symfony Messenger 工作进程开始工作。每条类型为 CheckContractPaymentAvailabilityMessage 的消息都会由对应的处理器(CheckContractPaymentAvailabilityMessageHandler)处理。
处理器充当极简的协调者:
public function __invoke(CheckContractPaymentAvailabilityMessage $message): void
{
$contractPaymentAvailability = $this->contractPaymentAvailabilityStorage
->getById($message->contractPaymentAvailabilityId);
try {
$this->contractCheckPaymentAvailabilityService
->checkContractAvailability($contractPaymentAvailability);
} catch (ContractExecutionFailedException $e) {
// 异常被静默处理,因为状态已经记录在数据库中
}
}
这种“委托给专门服务”的模式是最佳实践:处理器只负责基础设施(获取数据、捕获错误),业务逻辑则位于服务内部。
第3阶段:区块链交互
这一步是整个过程最有趣的部分。ContractCheckPaymentAvailabilityService 是验证系统的核心,它直接与 Stellar 区块链上的智能合约交互。
步骤详解
-
调用智能合约
服务调用部署在区块链上的特定智能合约函数(
check_reserve_balance):$trxResponse = $this->checkContractPaymentAvailabilityOperation ->checkContractPaymentAvailability($contractPaymentAvailability);该函数执行链上计算:检查投资者数量、下一个待付款项、储备金余额,并计算是否存在赤字。
合约中该函数的代码(使用 Rust 编写的 Soroban 合约)如下:
pub fn check_reserve_balance(env: Env) -> Result { require_admin(&env); let claims_map: Map = get_claims_map_or_new(&env); let project_balances: ContractBalances = get_balances_or_new(&env); let mut min_funds: i128 = 0; for (_addr, next_claim) in claims_map.iter() {
if next_claim.is_claim_next(&env) {
min_funds += next_claim.amount_to_pay;
}
}
if min_funds > 0 {
if project_balances.reserve
-
Result processing
(原文在此处截断;请继续描述服务如何解释返回值、更新
ContractPaymentAvailability实体,并触发任何必要的警报或纠正措施。)
智能合约储备金验证工作流
智能合约的响应以 XDR 格式(Stellar 使用的二进制格式)返回。我们使用 Soneso PHP Stellar SDK 对其进行解码:
$trxResult = $this->scContractResultBuilder
->getResultDataFromTransactionResponse($trxResponse);
$requiredFunds = $this->i128Handler
->fromI128ToPhpFloat(
$trxResult->getLo(),
$trxResult->getHi(),
$contractPaymentAvailability->getContract()
->getToken()
->getDecimals()
);
$requiredFunds 的数值至关重要:
- 0 – 一切正常。
- > 0 – 表示储备金中缺少的金额,需补足该数额才能覆盖下一笔付款。
3. 交易记录
Each blockchain interaction is recorded:
$contractTransaction = $this->contractTransactionEntityTransformer
->fromSuccessfulTransaction(
$contractPaymentAvailability->getContract()->getAddress(),
ContractNames::INVESTMENT->value,
ContractFunctions::check_reserve_balance->name,
[$trxResult],
$trxResponse->getTxHash(),
$trxResponse->getCreatedAt()
);
The resulting ContractTransaction provides:
- 完整可追溯性 – 我们可以审计每一次验证。
- 交易哈希 – 可在 Stellar 上验证。
- 精确时间戳 – 我们知道它何时发生。
- 结果 – 智能合约返回的内容。
4. 状态更新
如果检测到赤字($requiredFunds > 0),系统会阻止该合同:
if ($requiredFunds > 0) {
$contract = $contractPaymentAvailability->getContract();
$this->contractEntityTransformer->updateContractAsBlocked($contract);
$this->persistor->persist($contract);
}
合同会进入 BLOCKED 状态,其作用包括:
- 向管理员发出警报。
- 在 UI 中显示合同需要关注。
- 记录必须添加到储备金的确切金额。
UI 警告
在 assets/react/controllers/Contract/ViewContract.tsx 中,警告显示为:
{query.data.requiredReserveFunds && query.data.requiredReserveFunds > 0 ? (
⚠️ The contract requires adding {formatCurrencyFromValueAndTokenContract(
query.data.requiredReserveFunds,
query.data.tokenContract
)} to the reserve fund to ensure investor payments.
) : (
✓ The contract has capacity to serve payments.
)}
后续工作: 添加电子邮件通知,以在检测到赤字时立即提醒管理员。
5. 错误处理
如果出现故障(网络中断、智能合约错误等),我们会以受控的方式记录它:
catch (TransactionExceptionInterface $ex) {
$contractTransaction = $this->contractTransactionEntityTransformer
->fromFailedTransaction(
$contractPaymentAvailability->getContract()->getAddress(),
ContractNames::INVESTMENT->value,
ContractFunctions::check_reserve_balance->name,
$ex
);
// Mark the verification as failed
$this->contractPaymentAvailabilityTransformer
->updateContractPaymentAvalabilityAsFailed(
$contractPaymentAvailability,
$contractTransaction
);
}
ContractPaymentAvailability 实体 – 历史记录
每次验证都会通过 ContractPaymentAvailability 实体持久化到数据库:
class ContractPaymentAvailability
{
private ?int $id;
private ?Contract $contract; // Which contract was verified?
private ?float $requiredFunds; // Missing amount (if any)
private ?ContractTransaction $contractTransaction; // Associated blockchain transaction
private ?\DateTimeImmutable $checkedAt; // When it was verified
private ?\DateTimeImmutable $createdAt; // When it was requested
private ?string $status; // PENDING, PROCESSED, FAILED
}
此结构使我们能够:
- 查看合同的完整验证历史。
- 识别模式(例如,频繁的资金短缺)。
- 衡量处理时间。
- 生成报告和指标。
此架构的优势
- 可扩展性 – 异步处理使我们能够在不影响主应用的情况下验证大量合约。每个验证在各自的上下文中运行。
- 弹性 – 某个验证的失败不会影响其他验证;我们可以稍后重试失败的任务。
- 透明性 – 直接与链上智能合约交互,使计算可验证且不可篡改。任何人都可以审计其逻辑。
- 关注点分离
- Command – 标识工作。
- Handler – 处理消息基础设施。
- Service – 包含业务逻辑。
- Entities – 对数据建模。
每个组件都有明确的职责,且可以独立演进。
结论
我们展示了一种混合的链下/链上解决方案,该方案:
- 持续监控储备金健康状况。
- 将每一次验证记录在区块链和我们的数据库中。
- 在检测到赤字时自动响应,阻止合约并通知相关方。
这种方法提供了可扩展性、弹性、透明性以及清晰的关注点分离,为未来的增强功能(如自动电子邮件提醒和更丰富的报告)奠定了坚实的基础。
链下与链上交互
系统将 Symfony 与智能合约交互相结合,用于检查投资合约的支付能力。
- Off‑chain component – 负责调度、排队和状态管理。
- On‑chain component – 在 Stellar 区块链上执行实际的金融计算。
这种方法让我们能够兼顾两者的优势:
- Flexibility & familiarity of a traditional PHP framework for business logic.
- Transparency & immutability of blockchain for critical financial verifications.
接下来是什么?
在后续文章中,我们将探讨 Equillar 平台的其他方面,例如:
- 支付处理
- 储备基金管理
- 与 Stellar 网络的集成
敬请期待!
技术说明
本文描述了撰写时系统的内部运行情况。实现细节可能随时间演变,但基本的架构原则保持不变。