> ## Documentation Index
> Fetch the complete documentation index at: https://orru.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Anchoring a payment

> The commitment, the transaction, and the one encoding you must not get wrong.

Anchoring is the single on-chain action a payer takes: publish one hash per payment to Ethereum so the worker can later prove that payment happened. This page covers the contract, the exact commitment encoding, and how to deliver the slip a worker needs to build a proof.

## The contract

`PayerAnchor` on Ethereum Sepolia. Unowned, immutable, permissionless: anyone can call it, and nobody can upgrade it. It is a trust root, so it has no admin to compromise.

```text theme={null}
PayerAnchor  0x16EaB9DA91D2AEea1F1138A95E42C37d1D47B7d2
```

```solidity theme={null}
function anchorPayment(bytes32 commitment) external;
function anchorBatch(bytes32[] calldata commitments) external;  // max 32

event PaymentAnchored(address indexed payer, bytes32 indexed commitment);
```

Batch a payroll run into one call. One transaction is one verification downstream, so thirty separate calls cost thirty verifications instead of one.

## The commitment

```text theme={null}
commitment = keccak256(abi.encode(recipient, amount, period, salt))
```

<Warning>
  **`abi.encode`, not `abi.encodePacked`.** Four 32-byte words, 128 bytes total. The zero-knowledge circuit mirrors this exactly. One byte of difference and everything compiles, nothing verifies, and the failure surfaces somewhere that looks unrelated.
</Warning>

<ParamField path="recipient" type="address" required>
  The worker's wallet. This becomes the credential's subject.
</ParamField>

<ParamField path="amount" type="uint256" required>
  Base units of a six-decimal token. **\$2,500 is `2500000000`.** Must fit in a `uint64`; the circuit holds it as one.
</ParamField>

<ParamField path="period" type="uint256" required>
  Your pay-cycle counter, incrementing by one. Payer-local: your period 7 has nothing to do with anyone else's. The circuit requires three **consecutive** periods, so gaps make a window unprovable.
</ParamField>

<ParamField path="salt" type="bytes32" required>
  See below. This choice decides whether the commitment hides anything.
</ParamField>

## The salt decides your privacy

<Tabs>
  <Tab title="Random and secret (recommended)">
    32 bytes from a CSPRNG, kept by the payer and worker and never published.

    The commitment is then **hiding**: nobody can recover the amount from it, because they cannot guess the salt.

    ```ts theme={null}
    import { randomBytes } from "node:crypto";
    const salt = `0x${randomBytes(32).toString("hex")}`;
    ```

    <Warning>
      If every holder loses it, that payment can never be proven. There is no on-chain recovery, by design.
    </Warning>
  </Tab>

  <Tab title="Deterministic (on-chain payers only)">
    Derived from public values, e.g. `keccak256(abi.encode(payroll, recipient, period))`.

    The worker can recompute it without an off-chain channel, which is convenient. But a commitment with a guessable salt is **binding, not hiding**: anyone can brute-force the amount over a few thousand plausible salaries.

    Only reasonable when the amount is public anyway, which it is if you paid in an ERC-20 on a public chain.
  </Tab>
</Tabs>

## Anchoring

<CodeGroup>
  ```ts viem theme={null}
  const commitment = keccak256(
    encodeAbiParameters(
      [{ type: "address" }, { type: "uint256" }, { type: "uint256" }, { type: "bytes32" }],
      [recipient, amount, period, salt],
    ),
  );

  await wallet.writeContract({
    address: "0x16EaB9DA91D2AEea1F1138A95E42C37d1D47B7d2",
    abi: payerAnchorAbi,
    functionName: "anchorBatch",
    args: [commitments],
  });
  ```

  ```bash cast theme={null}
  COMMITMENT=$(cast keccak $(cast abi-encode \
    "f(address,uint256,uint256,bytes32)" \
    $RECIPIENT $AMOUNT $PERIOD $SALT))

  cast send 0x16EaB9DA91D2AEea1F1138A95E42C37d1D47B7d2 \
    "anchorBatch(bytes32[])" "[$COMMITMENT]" \
    --rpc-url $SEPOLIA_RPC_URL --account your-key
  ```
</CodeGroup>

Anchoring the same commitment twice from the same address reverts with `AlreadyAnchored`. Scoping is per payer, so two payers may legitimately anchor the same commitment.

## Delivering the slip

If you used a random salt, the worker needs the recipient, amount, period, salt and commitment to prove anything. Treat that complete record as payslip data and never publish a real preimage in documentation, source control or logs.

The shape of a slip, with made-up values. Nothing below is anchored anywhere; the commitment is what `shared/commitment.ts` returns for these inputs, so you can recompute it to check the encoding:

```json theme={null}
{
  "recipient": "0x1111111111111111111111111111111111111111",
  "amount":    "4200000000",
  "period":    "12",
  "salt":      "0x3b113115dd87ab2de1362a6c46f2702f7b5fced1d8484d748ba297b78c75dcda",
  "commitment":"0x892251be06412224f4c20996dac6b45ab4a387fe4355d14f03dae5e4f014cf85"
}
```

Any channel you already trust for payslips: the portal they log into, an encrypted export, your existing HR system. It is the same sensitivity as a payslip, because that is what it is.

<Note>
  Direct payer-to-worker delivery gives the strongest privacy because Orru never sees the preimage. In the current testnet demo, an authenticated Orru route may deliver or derive the demo slip for the connected wallet. The exact amount and salt are still never published on-chain, placed in the credential, shown to a lender or sent to a remote proving service, but the demo does not hide them from the Orru server.
</Note>

## Then what

A relayer picks up your `PaymentAnchored` event, waits for the block to be attested, and submits the Attestcoin proof to Creditcoin, typically within minutes. After that the worker can build a proof and issue a statement whenever they like.

You do not have to run that relayer, and it needs no permission from you.
