# usdcx-v1

{% code title=".usdcx-v1" lineNumbers="true" expandable="true" %}

```clarity
;; USDCx v1
;;
;; 此合约实现了 USDC xReserve 协议，用于在
;; Stacks 和其他链之间桥接 USDC。
;;
;; 此合约是铸造和销毁 USDCx 的主要入口。

;; 在恢复存款意图签名的
;; 公钥时发生错误。
(define-constant ERR_UNABLE_TO_RECOVER_PK (err u100))
;; 存款意图的长度无效。
(define-constant ERR_INVALID_DEPOSIT_BYTE_LENGTH (err u101))
;; 存款意图的金额大于 u128::max。
(define-constant ERR_INVALID_DEPOSIT_AMOUNT_TOO_HIGH (err u102))
;; 存款意图的最大费用大于 u128::max。
(define-constant ERR_INVALID_DEPOSIT_MAX_FEE_TOO_HIGH (err u103))
;; 存款意图的魔法字节无效。
(define-constant ERR_INVALID_DEPOSIT_INTENT_MAGIC (err u104))
;; 存款意图的钩子数据长度无效。
(define-constant ERR_INVALID_DEPOSIT_HOOK_DATA_LENGTH (err u105))
;; 存款意图的签名无效。
(define-constant ERR_INVALID_DEPOSIT_SIGNATURE (err u106))
;; 存款意图的版本无效。
(define-constant ERR_INVALID_DEPOSIT_VERSION (err u107))
;; 在考虑费用后，要铸造的 USDCx 数量为零。
(define-constant ERR_INVALID_DEPOSIT_AMOUNT_ZERO (err u108))
;; 铸造费用金额大于存款意图的最大费用。
(define-constant ERR_INVALID_DEPOSIT_FEE_AMOUNT_TOO_HIGH (err u109))
;; 存款意图的远程域无效。
(define-constant ERR_INVALID_DEPOSIT_REMOTE_DOMAIN (err u110))
;; 存款意图的远程代币无效。
(define-constant ERR_INVALID_DEPOSIT_REMOTE_TOKEN (err u111))
;; 存款意图的远程接收者无效。
(define-constant ERR_INVALID_DEPOSIT_REMOTE_RECIPIENT (err u112))
;; 这个 nonce 已在另一个存款中使用过
(define-constant ERR_INVALID_DEPOSIT_NONCE (err u113))
;; 最大费用大于或等于金额。
(define-constant ERR_INVALID_DEPOSIT_MAX_FEE_GTE_AMOUNT (err u114))
;; 存款意图的远程接收者长度无效。
(define-constant ERR_INVALID_DEPOSIT_REMOTE_RECIPIENT_LENGTH (err u115))
;; 提现金额小于最小提现金额。
(define-constant ERR_INVALID_WITHDRAWAL_AMOUNT_TOO_LOW (err u116))
;; 原生域不是支持的值（当前仅支持 0）
(define-constant ERR_INVALID_NATIVE_DOMAIN (err u117))

;; 存款编码的魔法字节
(define-constant DEPOSIT_INTENT_MAGIC 0x5a2e0acd)

;; 解析存款意图所支持的版本
(define-constant DEPOSIT_INTENT_VERSION u1)

;; 提现所支持的原生域
(define-constant ETHEREUM_NATIVE_DOMAIN u0)

;; 存款允许的 `domain`
(define-constant DOMAIN u10003)

;; 已使用 nonce 的映射
(define-map used-nonces
  (buff 32)
  bool
)

;; Circle 证明者公钥的映射
(define-map circle-attestors
  (buff 33)
  bool
)

;; 提现 USDCx 所需的最小金额
(define-data-var min-withdrawal-amount uint u0)

;; 从原始字节解析存款意图的辅助函数。
;; 此函数负责按照 Circle 规范解析存款意图。
;; Stacks 特定逻辑（例如将远程接收者转换为 principal）由其他函数处理。
;;
;; 如需完整验证，包括解析远程接收者并防止 nonce 重用，请使用
;; `parse-and-validate-deposit-intent`。
(define-read-only (parse-deposit-intent (deposit-intent (buff 320)))
  (begin
    (asserts! (>= (len deposit-intent) u240) ERR_INVALID_DEPOSIT_BYTE_LENGTH)
    (let (
        (magic (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u0 u4)) u4)))
        (version (buff-to-uint-be (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u4 u8)) u4))))
        (amount-left-bytes (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u8 u24)) u16)))
        (amount (buff-to-uint-be (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u24 u40)) u16))))
        (remote-domain (buff-to-uint-be (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u40 u44)) u4))))
        (remote-token (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u44 u76)) u32)))
        (remote-recipient (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u76 u108)) u32)))
        (local-token (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u108 u140)) u32)))
        (local-depositor (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u140 u172)) u32)))
        (max-fee-left-bytes (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u172 u188)) u16)))
        (max-fee (buff-to-uint-be (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u188 u204)) u16))))
        (nonce (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u204 u236)) u32)))
        (hook-data-len (buff-to-uint-be (unwrap-panic (as-max-len? (unwrap-panic (slice? deposit-intent u236 u240)) u4))))
      )
      (asserts! (is-eq magic DEPOSIT_INTENT_MAGIC)
        ERR_INVALID_DEPOSIT_INTENT_MAGIC
      )
      (asserts! (is-eq amount-left-bytes 0x00000000000000000000000000000000)
        ERR_INVALID_DEPOSIT_AMOUNT_TOO_HIGH
      )
      (asserts! (is-eq max-fee-left-bytes 0x00000000000000000000000000000000)
        ERR_INVALID_DEPOSIT_MAX_FEE_TOO_HIGH
      )
      (asserts! (is-eq (len deposit-intent) (+ u240 hook-data-len))
        ERR_INVALID_DEPOSIT_HOOK_DATA_LENGTH
      )
      (ok {
        magic: magic,
        version: version,
        amount: amount,
        remote-domain: remote-domain,
        remote-token: remote-token,
        remote-recipient: remote-recipient,
        local-token: local-token,
        local-depositor: local-depositor,
        max-fee: max-fee,
        nonce: nonce,
        hook-data: (if (is-eq hook-data-len u0)
          0x
          (unwrap-panic (as-max-len?
            (unwrap-panic (slice? deposit-intent u240 (+ u240 hook-data-len)))
            u80
          ))
        ),
      })
    )
  )
)

;; 从存款意图和签名中恢复证明者公钥。
;; 恢复过程是先对存款意图进行哈希（通过 `keccak256`）
;; 然后使用 `secp256k1-recover?` 函数。
(define-read-only (recover-deposit-intent-pk
    (deposit-intent (buff 320))
    (signature (buff 65))
  )
  (let (
      (hash (keccak256 deposit-intent))
      (recovered-pk (unwrap! (secp256k1-recover? hash signature) ERR_UNABLE_TO_RECOVER_PK))
    )
    (ok recovered-pk)
  )
)

;; 添加或移除一个 Circle 证明者。
;;
;; 只能由具有治理角色的调用者调用。
(define-public (add-or-remove-circle-attestor
    (public-key (buff 33))
    (enabled bool)
  )
  (begin
    ;; #[filter(public-key, enabled)]
    (try! (contract-call? .usdcx validate-protocol-caller 0x00 contract-caller))
    (map-set circle-attestors public-key enabled)
    (ok true)
  )
)

;; 恢复并验证存款意图签名。
;;
;; 首先恢复公钥（通过 `recover-deposit-intent-pk`）。
;; 然后，将该公钥与 `circle-attestors` 映射进行检查。
(define-read-only (verify-deposit-intent-signature
    (deposit-intent (buff 320))
    (signature (buff 65))
  )
  (begin
    ;; #[filter(deposit-intent, signature)]
    (let ((recovered-pk (try! (recover-deposit-intent-pk deposit-intent signature))))
      (asserts! (default-to false (map-get? circle-attestors recovered-pk))
        ERR_INVALID_DEPOSIT_SIGNATURE
      )
      (ok recovered-pk)
    )
  )
)

;; 将 32 字节转换为标准 principal。这将被序列化为
;; 1 个版本字节，加上 20 个哈希字节。然后再在左侧填充
;; 11 个 0x00 字节。
;;
;; 为了支持将合约作为接收者，`hook-data` 可以包含一个合约名称。
;; 要使用此功能，`hook-data` 必须是类型为以下内容的共识序列化缓冲区
;; { contract-name: (string-ascii 40) }。
;;
;; 如果 `hook-data` 无法反序列化，此函数会回退
;; 为使用标准 principal。
(define-read-only (get-remote-recipient
    (remote-recipient-bytes (buff 32))
    (hook-data (buff 80))
  )
  (let (
      (valid-len (asserts! (is-eq (len remote-recipient-bytes) u32)
        ERR_INVALID_DEPOSIT_REMOTE_RECIPIENT_LENGTH
      ))
      (version-byte (unwrap-panic (element-at? remote-recipient-bytes u11)))
      (hash-bytes (unwrap-panic (as-max-len? (unwrap-panic (slice? remote-recipient-bytes u12 u32)) u20)))
      ;; 避免当 `hook-data` 为空时触发 VM 运行时错误：
      (hook-contract-name (if (is-eq (len hook-data) u0)
        none
        (from-consensus-buff? { contract-name: (string-ascii 40) } hook-data)
      ))
    )
    ;; 必须以 0x00 作为填充
    (asserts!
      (is-eq
        (unwrap-panic (as-max-len? (unwrap-panic (slice? remote-recipient-bytes u0 u11)) u11))
        0x0000000000000000000000
      )
      ERR_INVALID_DEPOSIT_REMOTE_RECIPIENT
    )
    (ok (unwrap!
      (match hook-contract-name
        contract-name-tup (principal-construct? version-byte hash-bytes
          (get contract-name contract-name-tup)
        )
        (principal-construct? version-byte hash-bytes)
      )
      ERR_INVALID_DEPOSIT_REMOTE_RECIPIENT
    ))
  )
)

;; `.usdcx` 合约地址的 32 字节编码版本。
;; 在存款意图中，这必须用作 `remote-token` 字段。
(define-read-only (get-valid-remote-token)
  (concat 0x00000000
    (unwrap-panic (as-max-len? (unwrap-panic (to-consensus-buff? .usdcx)) u28))
  )
)

;; 解析并验证存款意图的辅助函数。
;;
;; 除了基本解析（通过 `parse-deposit-intent` 完成）之外，此函数
;; 还验证某些 Stacks 特定字段，例如
;; 远程代币、远程域、远程接收者和版本。
;;
;; 此外，此函数还验证 `amount` 和 `max-fee` 字段。
(define-read-only (parse-and-validate-deposit-intent (deposit-intent (buff 320)))
  (let (
      (parsed-intent (try! (parse-deposit-intent deposit-intent)))
      (remote-recipient (try! (get-remote-recipient (get remote-recipient parsed-intent)
        (get hook-data parsed-intent)
      )))
      (amount (get amount parsed-intent))
    )
    (asserts! (is-eq (get remote-token parsed-intent) (get-valid-remote-token))
      ERR_INVALID_DEPOSIT_REMOTE_TOKEN
    )
    (asserts! (> amount u0) ERR_INVALID_DEPOSIT_AMOUNT_ZERO)
    (asserts! (is-eq (get remote-domain parsed-intent) DOMAIN)
      ERR_INVALID_DEPOSIT_REMOTE_DOMAIN
    )
    (asserts! (is-eq (get version parsed-intent) DEPOSIT_INTENT_VERSION)
      ERR_INVALID_DEPOSIT_VERSION
    )
    (asserts! (>= amount (get max-fee parsed-intent))
      ERR_INVALID_DEPOSIT_MAX_FEE_GTE_AMOUNT
    )
    (asserts! (is-none (map-get? used-nonces (get nonce parsed-intent)))
      ;; 这个 nonce 已在另一个存款中使用过
      ERR_INVALID_DEPOSIT_NONCE
    )
    (ok (merge parsed-intent { remote-recipient: remote-recipient }))
  )
)

;; 使用存款意图铸造 USDCx。
;; 这是铸造 USDCx 的主要入口。
;;
;; 除了 `parse-and-validate-deposit-intent` 和
;; `verify-deposit-intent-signature` 执行的验证之外，此函数还验证由
;; 调用者提供的 `fee-amount`，以确保不会出现零金额铸造。
;;
;; 如果 `fee-amount` 非零（且小于存款的 `max-fee`），
;; 此函数会向调用者铸造 `fee-amount` 的 USDCx。这允许
;; 除存款接收者之外的账户承担铸造所需的 STX 费用。
(define-public (mint
    (deposit-intent (buff 320))
    (signature (buff 65))
    (fee-amount uint)
  )
  (let (
      (parsed-intent (try! (parse-and-validate-deposit-intent deposit-intent)))
      (recovered-pk (try! (verify-deposit-intent-signature deposit-intent signature)))
      (mint-amount (- (get amount parsed-intent) fee-amount))
    )
    (asserts! (>= (get max-fee parsed-intent) fee-amount)
      ERR_INVALID_DEPOSIT_FEE_AMOUNT_TOO_HIGH
    )
    ;; 铸造给接收者
    (if (is-eq mint-amount u0)
      true
      (try! (contract-call? .usdcx protocol-mint mint-amount
        (get remote-recipient parsed-intent)
      ))
    )
    (if (is-eq fee-amount u0)
      true
      (try! (contract-call? .usdcx protocol-mint fee-amount tx-sender))
    )
    (map-set used-nonces (get nonce parsed-intent) true)
    (print {
      topic: "mint",
      parsed-intent: parsed-intent,
      attestor-pk: recovered-pk,
      mint-amount: mint-amount,
      fee-amount: fee-amount,
    })
    (ok true)
  )
)

;; 设置最小提现金额。
;;
;; 只能由具有自定义角色 `0x04` 的调用者调用。
(define-public (set-min-withdrawal-amount (new-min-withdrawal-amount uint))
  (begin
    (try! (contract-call? .usdcx validate-protocol-caller 0x04 contract-caller))
    (var-set min-withdrawal-amount new-min-withdrawal-amount)
    (ok true)
  )
)

(define-read-only (get-min-withdrawal-amount)
  (var-get min-withdrawal-amount)
)

;; 为了从协议中提取 USDCx 而销毁 USDCx。
;;
;; 此函数会销毁调用者账户中的 USDCx，并发出 `burn` 事件。
;;
;; 金额必须大于或等于最小提现金额。
;;
;; `native-domain` 必须是受支持的值（当前仅支持 `ETHEREUM_NATIVE_DOMAIN` (u0)）。
(define-public (burn
    (amount uint)
    (native-domain uint)
    (native-recipient (buff 32))
  )
  (begin
    (asserts! (>= amount (var-get min-withdrawal-amount))
      ERR_INVALID_WITHDRAWAL_AMOUNT_TOO_LOW
    )
    (asserts! (is-eq native-domain ETHEREUM_NATIVE_DOMAIN)
      ERR_INVALID_NATIVE_DOMAIN
    )
    (try! (contract-call? .usdcx protocol-burn amount tx-sender))
    (print {
      topic: "burn",
      native-domain: native-domain,
      native-recipient: native-recipient,
      sender: tx-sender,
      amount: amount,
    })
    (ok true)
  )
)

```

{% endcode %}

## **USDCx-v1 合约摘要**

这个 `usdcx-v1` 合约实现了 **USDC xReserve 协议** 用于在 Stacks 与外部链之间转移 USDC。它充当 **铸造和销毁 USDCx 的主要入口** ，基于 Circle 发行的存款意图。

此合约处理：

* 解析和验证存款意图负载
* 恢复并验证 Circle 证明者签名
* 强制执行基于 nonce 的重放保护
* 通过 `usdcx` 代币合约
* 销毁 USDCx 以发起提现吗
* 管理 Circle 证明者密钥
* 处理 Stacks 特定的接收者转换
* 为赞助铸造应用费用逻辑
* 管理最小提现阈值

它与主 `usdcx` 代币合约协同工作，该合约强制执行协议角色（`铸造`, `治理`，等等）。所有铸造/销毁都通过 `protocol-mint` 和 `protocol-burn`.

### 铸造 USDCx

铸造 USDCx 的入口是通过 `铸造` 函数。调用者提供一个序列化的存款意图，以及一个签名，这两者都在链下作为桥接过程的一部分接收。存款意图根据 xReserve 规范进行解析。

有几个 Stacks 特定元素：

* 这个 `remote-token` 必须是 principal 的共识序列化字节 `.usdcx` （其中部署者地址依赖于网络），并在左侧填充 0x。
* 这个 `remote-domain` 对于 Stacks 总是 `10003`.
* 因为 Stacks 只支持 `u128` 整数，所以如果存款意图中的任何 64 字节整数大于 `u128::max`，反序列化函数会抛出错误。这符合 xReserve 规范。

### **验证证明**

这个 `.usdcx-v1` 合约保留一个 `circle-attestors` 映射，用于跟踪有效证明者的公钥。当提供存款意图时，它必须由该映射中的某个公钥签名。

### 销毁 USDCx

要将 USDCx 提现到另一条链，用户调用 `burn`。指定数量的 USDCx 会从他们的 Stacks 账户中销毁。系统会发出一个 `print` 事件，该事件用于在链下触发销毁证明。

这个 `.usdcx-v1` 合约存储一个最小金额变量。用户必须至少提现这个金额，否则销毁失败。具有角色 `0x04` 的账户可以更新此变量。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.stacks.co/learn/zh/bridging/usdcx/contracts/usdcx-v1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
