AinePay
EN中文
非托管

非托管设计

AinePay 根据商户的归集地址确定性地生成其用户的支付地址,该地址是一个区块链合约地址,合约的内容是凡是转入到这个地址的资金都只能归集到商户的归集地址且能被任何人归集到商户归集地址。因此用户资金不需要托管在任何平台,数学和区块链保证了资金的路径。

非托管地址架构

  1. 归集地址:商户控制的地址,资金最终被归集到该地址,商户需要在商户管理后台进行设置。
  2. 用户地址:向用户展示的支付地址,这是商户归集地址通过确定性关系生成的一个合约地址,合约的内容是凡是转入到这个地址的资金都只能归集到商户的归集地址且能被任何人归集到商户归集地址。
  3. ForwarderImpl:用户地址的合约模板,每一个用户地址的合约内容与这个地址相同。
  4. ForwarderFactory:按照 ForwarderImpl 模板生成用户地址。

非托管流程如何运作

  1. 商户在自己的管理后台配置一个或多个活跃的归集地址。
  2. AinePay 为该商户的客户创建一个唯一的用户支付地址。
  3. 客户支付到该专有用户支付地址,因此 AinePay 可以区分是哪个用户进行了存款。
  4. 存款记录到 AinePay 的账户记录中,用于该用户支付订单。
  5. 当用户地址余额达到自动归集阈值且商户状态为 ACTIVE 时,AinePay 可以自动将资金归集到商户的归集地址(合约保证了任何人都无法将资金归集到其他地址)。在当前配置中,归集阈值为 1 USD。

这种设计同时满足两个业务目标。首先,每个用户获得一个唯一的支付地址,因此转账可以被正确归因并记入正确的用户。其次,当资金到达 AinePay 设置的阈值时,被设计为只能流向商户预配置的归集地址。

即使 AinePay 不可用,商户仍然可以从用户地址向其自己的归集地址归集资金而无需依赖任何人,在归集操作中 AinePay 并不具有超越其他任何人对于该合约的权限。

CREATE2 确定性地址模型

用户支付地址通过 CREATE2 模型计算生成。在当前实现中,该地址由 ForwarderFactory、ForwarderImpl、merchantId、userId、商户归集地址、version 和 chainId 确定性派生。

address = keccak256(0xff ++ factory ++ salt ++ keccak256(init_code))[12:]
salt: keccak256(abi.encode(keccak256(merchantId), keccak256(userId), version, destination, chainId))

这是用于派生用户支付地址的唯一标识符,它由商户、用户、转发器版本、归集地址和链 ID 构建。如果其中任何输入变更,派生的地址将不同。

init_code:指向 ForwarderImpl 的最小代理创建代码。它定义了将部署什么类型的合约以及它将指向哪个实现。

参考文档

如何验证

商户可以使用如下示例代码独立验证 AinePay 分配的用户地址是否与预期匹配,以确定该地址的任何转入将最终归集到自己的归集地址:

import { ethers } from "ethers";

function predictForwarderAddress(
  factory,
  implementation,
  merchantId,
  userId,
  destination,
  version,
  chainId
) {
  const impl = implementation.toLowerCase().replace(/^0x/, "");
  const initCodeHash = ethers.keccak256(
    "0x3d602d80600a3d3981f3" +
      "363d3d373d3d3d363d73" +
      impl +
      "5af43d82803e903d91602b57fd5bf3"
  );

  const salt = ethers.keccak256(
    ethers.AbiCoder.defaultAbiCoder().encode(
      ["bytes32", "bytes32", "uint256", "address", "uint256"],
      [
        ethers.keccak256(ethers.toUtf8Bytes(merchantId)),
        ethers.keccak256(ethers.toUtf8Bytes(userId)),
        BigInt(version),
        destination,
        BigInt(chainId)
      ]
    )
  );

  const packed = ethers.keccak256(
    ethers.concat(["0xff", factory, salt, initCodeHash])
  );

  return ethers.getAddress(ethers.dataSlice(packed, 12));
}

const FACTORY = "0x06559ab75cd906e2ecd9c3e91459eea558e2ec1b";
const IMPLEMENTATION = "0x42eb2a5b755551d5f386f2c79807abd438341557";
const ASSIGNED_USER_ADDRESS = "<ASSIGNED_USER_ADDRESS>";
const merchantId = "20001";
const userId = "U_90001";
const destination = "<DESTINATION_ADDRESS>";
const version = 1;
const chainId = 1;

const predicted = predictForwarderAddress(
  FACTORY,
  IMPLEMENTATION,
  merchantId,
  userId,
  destination,
  version,
  chainId
);

const matched =
  ethers.getAddress(predicted) === ethers.getAddress(ASSIGNED_USER_ADDRESS);

console.log({ predicted, matched });

合约地址

专属部署

AinePay 可以根据需要为单个商户部署专属合约。如需此设置,请联系 support@ainepay.com