# usdcx-v1

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

```clarity
;; USDCx v1
;;
;; Este contrato implementa el protocolo USDC xReserve para puentear USDC entre
;; Stacks y otras cadenas.
;;
;; Este contrato es el punto de entrada principal para acuñar y quemar USDCx.

;; Se produjo un error al recuperar la clave pública de una firma de intención
;; de depósito.
(define-constant ERR_UNABLE_TO_RECOVER_PK (err u100))
;; La longitud de la intención de depósito no es válida.
(define-constant ERR_INVALID_DEPOSIT_BYTE_LENGTH (err u101))
;; La cantidad de la intención de depósito es mayor que u128::max.
(define-constant ERR_INVALID_DEPOSIT_AMOUNT_TOO_HIGH (err u102))
;; La comisión máxima de la intención de depósito es mayor que u128::max.
(define-constant ERR_INVALID_DEPOSIT_MAX_FEE_TOO_HIGH (err u103))
;; Los bytes mágicos de la intención de depósito no son válidos.
(define-constant ERR_INVALID_DEPOSIT_INTENT_MAGIC (err u104))
;; La longitud de los datos de hook de la intención de depósito no es válida.
(define-constant ERR_INVALID_DEPOSIT_HOOK_DATA_LENGTH (err u105))
;; La firma de la intención de depósito no es válida.
(define-constant ERR_INVALID_DEPOSIT_SIGNATURE (err u106))
;; La versión de la intención de depósito no es válida.
(define-constant ERR_INVALID_DEPOSIT_VERSION (err u107))
;; Después de contabilizar las comisiones, la cantidad de USDCx a acuñar es cero.
(define-constant ERR_INVALID_DEPOSIT_AMOUNT_ZERO (err u108))
;; La cantidad de comisión de la acuñación es mayor que la comisión máxima de la intención de depósito.
(define-constant ERR_INVALID_DEPOSIT_FEE_AMOUNT_TOO_HIGH (err u109))
;; El dominio remoto de la intención de depósito no es válido.
(define-constant ERR_INVALID_DEPOSIT_REMOTE_DOMAIN (err u110))
;; El token remoto de la intención de depósito no es válido.
(define-constant ERR_INVALID_DEPOSIT_REMOTE_TOKEN (err u111))
;; El destinatario remoto de la intención de depósito no es válido.
(define-constant ERR_INVALID_DEPOSIT_REMOTE_RECIPIENT (err u112))
;; Este nonce ya se ha usado en un depósito diferente
(define-constant ERR_INVALID_DEPOSIT_NONCE (err u113))
;; La comisión máxima es mayor o igual que la cantidad.
(define-constant ERR_INVALID_DEPOSIT_MAX_FEE_GTE_AMOUNT (err u114))
;; La longitud del destinatario remoto de la intención de depósito no es válida.
(define-constant ERR_INVALID_DEPOSIT_REMOTE_RECIPIENT_LENGTH (err u115))
;; La cantidad de retirada es menor que la cantidad mínima de retirada.
(define-constant ERR_INVALID_WITHDRAWAL_AMOUNT_TOO_LOW (err u116))
;; El dominio nativo no es el valor admitido (actualmente solo 0)
(define-constant ERR_INVALID_NATIVE_DOMAIN (err u117))

;; Bytes mágicos para la codificación del depósito
(define-constant DEPOSIT_INTENT_MAGIC 0x5a2e0acd)

;; Versión admitida para analizar intenciones de depósito
(define-constant DEPOSIT_INTENT_VERSION u1)

;; Dominio nativo admitido para retiradas
(define-constant ETHEREUM_NATIVE_DOMAIN u0)

;; `domain` permitido para depósitos
(define-constant DOMAIN u10003)

;; Mapa de nonces usados
(define-map used-nonces
  (buff 32)
  bool
)

;; Mapa de claves públicas de atestadores de Circle
(define-map circle-attestors
  (buff 33)
  bool
)

;; Cantidad mínima requerida para retirar USDCx
(define-data-var min-withdrawal-amount uint u0)

;; Función auxiliar para analizar una intención de depósito a partir de bytes sin procesar.
;; Esta función se encarga de analizar la intención de depósito según la especificación de Circle.
;; La lógica específica de Stacks (como convertir el destinatario remoto en un principal) la manejan otras funciones.
;;
;; Para una validación completa, incluido el análisis del destinatario remoto y evitar la reutilización de nonces, usa
;; `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
          ))
        ),
      })
    )
  )
)

;; Recuperar la clave pública del atestador a partir de una intención de depósito y una firma.
;; La recuperación se realiza mediante el hash de la intención de depósito (con `keccak256`)
;; y luego usando la función `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)
  )
)

;; Añadir o eliminar un atestador de Circle.
;;
;; Solo puede ser llamada por un llamador con el rol de gobernanza.
(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)
  )
)

;; Recuperar y verificar una firma de intención de depósito.
;;
;; Primero se recupera la clave pública (mediante `recover-deposit-intent-pk`).
;; Luego, la clave pública se comprueba contra el mapa `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)
    )
  )
)

;; Convertir 32 bytes en un principal estándar. Esto se serializa como
;; 1 byte de versión, más 20 bytes de hash. Luego se rellena a la izquierda
;; con 11 bytes de 0x00.
;;
;; Para admitir contratos como destinatarios, `hook-data` puede contener un nombre de contrato.
;; Para usar esta funcionalidad, `hook-data` DEBE ser un buffer serializado por consenso
;; del tipo { contract-name: (string-ascii 40) }.
;;
;; Si `hook-data` no se puede deserializar, esta función recurre
;; al uso de un principal estándar.
(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)))
      ;; Evita un error de tiempo de ejecución de la VM cuando `hook-data` está vacío:
      (hook-contract-name (if (is-eq (len hook-data) u0)
        none
        (from-consensus-buff? { contract-name: (string-ascii 40) } hook-data)
      ))
    )
    ;; Debe tener 0x00 como relleno
    (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
    ))
  )
)

;; Versión codificada en 32 bytes de la dirección del contrato `.usdcx`.
;; Esto debe usarse en las intenciones de depósito como el campo `remote-token`.
(define-read-only (get-valid-remote-token)
  (concat 0x00000000
    (unwrap-panic (as-max-len? (unwrap-panic (to-consensus-buff? .usdcx)) u28))
  )
)

;; Función auxiliar para analizar y validar una intención de depósito.
;;
;; Además del análisis básico (realizado mediante `parse-deposit-intent`), esta función
;; también valida ciertos campos específicos de Stacks, como el
;; token remoto, el dominio remoto, el destinatario remoto y la versión.
;;
;; Además, esta función valida los campos `amount` y `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)))
      ;; Este nonce ya se ha usado en un depósito diferente
      ERR_INVALID_DEPOSIT_NONCE
    )
    (ok (merge parsed-intent { remote-recipient: remote-recipient }))
  )
)

;; Acuñar USDCx usando una intención de depósito.
;; Este es el punto de entrada principal para acuñar USDCx.
;;
;; Además de la validación realizada por `parse-and-validate-deposit-intent`, y
;; `verify-deposit-intent-signature`, esta función también valida el `fee-amount`
;; proporcionado por el llamador para garantizar que no sean posibles acuñaciones por cantidad cero.
;;
;; Si `fee-amount` no es cero (y es menor que la `max-fee` del depósito),
;; esta función acuñará `fee-amount` de USDCx para el llamador. Esto permite
;; que cuentas distintas del destinatario del depósito cubran la comisión STX necesaria para acuñar.
(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
    )
    ;; acuñar al destinatario
    (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)
  )
)

;; Establecer la cantidad mínima de retirada.
;;
;; Solo puede ser llamada por un llamador con el rol personalizado `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)
)

;; Quemar USDCx con el propósito de retirarlo del protocolo.
;;
;; Esta función quema USDCx de la cuenta del llamador y emite un evento `burn`.
;;
;; La cantidad debe ser mayor o igual que la cantidad mínima de retirada.
;;
;; `native-domain` debe ser un valor admitido (actualmente solo `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 %}

## **Resumen del contrato USDCx-v1**

El `usdcx-v1` el contrato implementa el **protocolo USDC xReserve** para mover USDC entre Stacks y cadenas externas. Sirve como el **punto de entrada principal para acuñar y quemar USDCx** basado en intenciones de depósito emitidas por Circle.

Este contrato maneja:

* Analizar y validar cargas útiles de intenciones de depósito
* Recuperar y verificar firmas de atestadores de Circle
* Aplicar protección contra reutilización basada en nonce
* Acuñar USDCx a través del `usdcx` contrato de token
* Quemar USDCx para iniciar retiradas
* Gestionar las claves de los atestadores de Circle
* Manejar conversiones de destinatarios específicas de Stacks
* Aplicar lógica de comisiones para acuñaciones patrocinadas
* Gestionar umbrales mínimos de retirada

Funciona en conjunto con el principal `usdcx` contrato de token, que aplica los roles del protocolo (`mint`, `governance`, etc.). Toda la acuñación/quema ocurre a través de `protocol-mint` y `protocol-burn`.

### Acuñación de USDCx

El punto de entrada para acuñar USDCx es a través de la `mint` función. El llamador proporciona una intención de depósito serializada, junto con una firma, ambas recibidas fuera de la cadena como parte del proceso de puente. La intención de depósito se analiza según la especificación xReserve.

Hay algunos elementos específicos de Stacks:

* El `remote-token` de la intención de depósito DEBE ser los bytes serializados por consenso del principal `.usdcx` (donde la dirección del desplegador depende de la red), con 0x rellenado a la izquierda.
* El `remote-domain` para Stacks siempre es `10003`.
* Debido a que Stacks solo admite `u128` enteros, las funciones de deserialización generan un error si cualquier entero de 64 bytes en la intención de depósito es mayor que `u128::max`. Esto está permitido según la especificación xReserve.

### **Verificación de atestaciones**

El `.usdcx-v1` el contrato mantiene un `circle-attestors` mapa para llevar un registro de las claves públicas de los atestadores válidos. Cuando se proporciona una intención de depósito, debe estar firmada por una clave pública de este mapa.

### Quema de USDCx

Para retirar USDCx a otra cadena, los usuarios llaman a `burn`. La cantidad especificada de USDCx se quema de su cuenta de Stacks. Se emite un `print` evento, que se usa para desencadenar una atestación de quema fuera de la cadena.

El `.usdcx-v1` contrato almacena una variable de cantidad mínima. Los usuarios deben retirar al menos esta cantidad, o la quema falla. Las cuentas con el rol `0x04` pueden actualizar esta variable.


---

# 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/es/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.
