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

# Developer Guide

> Integrate PAXGy minting, redemption, and the share rate on Ethereum mainnet.

# PAXGy Integration

This guide covers integrating PAXGy on-chain: checking access, reading the share rate, minting with PAXG, and redeeming back to PAXG.

<Info>
  Minting and redemption are available on **Ethereum mainnet** only. PAXGy can be held and transferred on Ethereum and X Layer. See [Supported networks](/v1.0.0/intro/products/paxgy/overview#supported-networks).
</Info>

<Info>
  Prefer not to manage ABIs and addresses yourself? [API Calldata Integration](/v1.0.0/intro/products/paxgy/api-calldata/index) returns ready-to-sign transactions for every flow on this page.
</Info>

## Prerequisites

* An **approved wallet address** (see [Access](/v1.0.0/intro/products/paxgy/overview#access)). Minting and redemption calls from a non-approved address will revert.
* An Ethereum mainnet RPC endpoint.
* A wallet library (viem, ethers, wagmi, etc.).
* PAXG to deposit (for minting).

<Warning>
  Addresses and ABIs below are for Ethereum mainnet. Always confirm the current addresses with your Paxos Labs contact at integration time before moving funds.
</Warning>

## Contract addresses (Ethereum mainnet)

| Contract                        | Purpose                                      | Address                                      |
| ------------------------------- | -------------------------------------------- | -------------------------------------------- |
| PAXGy                           | The PAXGy token (ERC-20, 18 decimals)        | `0x6c6494Fd9962eB98B94ffA48F6679058F820700e` |
| PAXG                            | Underlying gold-backed asset (deposit token) | `0x45804880De22913dAFE09f4980848ECE6EcbAf78` |
| Deposit entrypoint              | Mint PAXGy by depositing PAXG                | `0xF6dE922D08cF9Ee4e7cE25ca1f3936EC1AA1f62C` |
| Withdraw queue                  | Submit and track redemption requests         | `0x69e0BF658bE5600864A946b4e5B911736D248FB4` |
| Rate oracle                     | Reads the current PAXGy → PAXG share rate    | `0x397e38359f169748a02bc11f98D4f451FE88C1fd` |
| Deposit fee module              | Quotes the mint fee                          | `0x70d05F2026E8a78451f2e0dC913145467E1Df6E0` |
| Withdraw fee module             | Quotes the redemption fee                    | `0xc5271217AE835e3054366c2aC3D04b97FCAcDF4A` |
| Cross-chain transfer entrypoint | Moves PAXGy between supported chains         | `0x41AB5d8387e60E30AACCF11357C306f6d875A6f7` |

### Contract addresses (X Layer, chain ID 196)

| Contract                        | Purpose                               | Address                                      |
| ------------------------------- | ------------------------------------- | -------------------------------------------- |
| PAXGy                           | The PAXGy token (ERC-20, 18 decimals) | `0x6c6494Fd9962eB98B94ffA48F6679058F820700e` |
| Cross-chain transfer entrypoint | Moves PAXGy to and from Ethereum      | `0x41AB5d8387e60E30AACCF11357C306f6d875A6f7` |

<Warning>
  **PAXG does not exist on X Layer.** There is no asset to mint from or redeem into, so minting and redemption are Ethereum-only. X Layer is hold-and-transfer only: mint on Ethereum and transfer across, and transfer back to Ethereum before redeeming.
</Warning>

## Checking access

A wallet must be approved before it can mint or redeem. If your integration surfaces mint/redeem actions, gate them on the wallet's approval status and poll it, so that access granted by Paxos Labs takes effect without a reconnect. The exact approval check (contract and parameters) is provided during onboarding.

## Reading the share rate

PAXGy accrues value through a share rate that increases over time, so one PAXGy is always worth an increasing amount of PAXG. Read the current rate from the **Rate oracle** to preview mints and redemptions:

```typescript theme={null}
import { createPublicClient, http, parseAbi } from 'viem';
import { mainnet } from 'viem/chains';

const client = createPublicClient({ chain: mainnet, transport: http() });

const RATE_ORACLE = '0x397e38359f169748a02bc11f98D4f451FE88C1fd';
const PAXG = '0x45804880De22913dAFE09f4980848ECE6EcbAf78';

// Returns the PAXG value of one whole PAXGy share (18 decimals)
const rate = await client.readContract({
  address: RATE_ORACLE,
  abi: parseAbi(['function getRateInQuoteSafe(address quote) view returns (uint256)']),
  functionName: 'getRateInQuoteSafe',
  args: [PAXG],
});
```

<Info>
  Treat any client-side conversion of PAXGy ↔ PAXG as an estimate derived from this rate.
</Info>

## Minting PAXGy

Minting is a two-step flow: approve PAXG to the deposit entrypoint, then deposit.

1. **Approve PAXG.** Grant the deposit entrypoint an allowance for the PAXG amount you intend to deposit.

   ```typescript theme={null}
   import { erc20Abi, zeroAddress } from 'viem';

   const DEPOSIT_ENTRYPOINT = '0xF6dE922D08cF9Ee4e7cE25ca1f3936EC1AA1f62C';

   await walletClient.writeContract({
     address: PAXG,
     abi: erc20Abi,
     functionName: 'approve',
     args: [DEPOSIT_ENTRYPOINT, depositAmount],
   });
   ```

2. **Deposit.** Call the deposit entrypoint with the PAXG amount and a minimum PAXGy amount to protect against rate movement.

   ```typescript theme={null}
   const DEPOSIT_ABI = parseAbi([
     'struct Attestation { string uuid; uint256 expiration; address attester; bytes signature; }',
     'function deposit(address depositAsset, uint256 depositAmount, uint256 minimumMint, address to, bytes distributorCode, Attestation _attestation) returns (uint256 shares)',
   ])

   await walletClient.writeContract({
     address: DEPOSIT_ENTRYPOINT,
     abi: DEPOSIT_ABI,
     functionName: 'deposit',
     args: [
       PAXG,
       depositAmount,
       minimumMint,
       recipient,
       '0x', // no distributor code
       { uuid: '', expiration: 0n, attester: zeroAddress, signature: '0x' },
     ],
   })
   ```

<Info>
  The trailing attestation tuple is left empty for PAXGy, which gates access at the wallet level rather than per deposit. Pass the empty values shown above.
</Info>

### Sizing `minimumMint`

`minimumMint` is your floor on PAXGy received. Two things move between reading the rate and the transaction landing:

* **The share rate**, which is read when you prepare and can change before the transaction lands.
* **The deposit fee**, which is derived from the live PAXG:gold price. When PAXG trades below its gold peg, a fee is charged equal to that shortfall, so the depositor is credited at the lower of peg and market. When PAXG is at or above peg the fee is zero.

Compute the expected shares from the rate, subtract the fee the module quotes, then apply a tolerance:

```typescript theme={null}
const FEE_ABI = parseAbi([
  'function calculateOfferFees(uint256 amount, address offerAsset, address wantAsset, address receiver) view returns (uint256)',
])

const DEPOSIT_FEE_MODULE = '0x70d05F2026E8a78451f2e0dC913145467E1Df6E0'
const PAXGY = '0x6c6494Fd9962eB98B94ffA48F6679058F820700e'

// The same call the deposit entrypoint makes internally.
const fee = await client.readContract({
  address: DEPOSIT_FEE_MODULE,
  abi: FEE_ABI,
  functionName: 'calculateOfferFees',
  args: [depositAmount, PAXG, PAXGY, recipient],
})

const net = depositAmount - fee
const expected = (net * 10n ** 18n) / rate
const minimumMint = expected - (expected * BigInt(slippageBps)) / 10_000n
```

<Warning>
  The fee module reverts when its price feed is stale. That is deliberate, because it blocks deposits priced on stale data, but it means `calculateOfferFees` is not guaranteed to return. Handle the revert rather than treating it as a network error.
</Warning>

<Info>
  PAXG can carry an on-transfer fee. If one is ever enabled, the vault receives less than `depositAmount`; size `minimumMint` with that in mind as well.
</Info>

## Redeeming PAXGy

Redemption is **not instant**. You submit a withdrawal request to the **Withdraw queue** offering PAXGy for PAXG. The request is fulfilled and PAXG is delivered to your wallet.

* **Submit.** `submitOrder` creates a redemption request. Each request is represented on-chain as an order NFT owned by your wallet.
* **Track.** `getOrderStatus` returns the request's current state.
* **Cancel.** `cancelOrder` cancels a request while it is still pending (not yet processed).

Order status values:

| Status | Meaning                     |
| ------ | --------------------------- |
| 0      | Not found                   |
| 1      | Pending (open, cancellable) |
| 2 / 3  | Complete                    |
| 4      | Pending refund              |
| 5      | Complete, refunded          |
| 6      | Failed transfer, refunded   |

<Warning>
  Do not assume redemption settles in the same transaction. Track the order status and handle the refund states (4–6), which return your PAXGy if a request cannot be fulfilled.
</Warning>

## Cross-chain (Ethereum ↔ X Layer)

PAXGy balances move between Ethereum (chain ID 1) and X Layer (chain ID 196) via [Chainlink CCIP](https://docs.chain.link/ccip). The tokens are burned on the source chain and issued on the destination. There is no wrapped representation.

X Layer supports exactly these two operations on PAXGy: receiving it (issued on arrival) and sending it back (burned on departure). Minting from PAXG and redeeming to PAXG stay on Ethereum, because PAXG itself is not deployed on X Layer.

Transfers go through the **cross-chain transfer entrypoint**. Both the quote and the transfer take the same routing struct:

```typescript theme={null}
const BRIDGE_ABI = parseAbi([
  'struct BridgeData { uint32 chainSelector; address destinationChainReceiver; address bridgeFeeToken; uint64 messageGas; bytes data; }',
  'function previewFee(uint256 shareAmount, BridgeData data) view returns (uint256 fee)',
  'function bridge(uint256 shareAmount, BridgeData data) payable returns (bytes32 messageId)',
])

const BRIDGE_ENTRYPOINT = '0x41AB5d8387e60E30AACCF11357C306f6d875A6f7'

// The fee token sentinel for paying in the chain's native token.
const NATIVE = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'

const data = {
  chainSelector: 196,        // the destination chain's EVM chain ID
  destinationChainReceiver: recipient,
  bridgeFeeToken: NATIVE,
  messageGas: 100_000n,
  data: '0x',
}
```

Two details that are easy to get wrong:

* **`chainSelector` is the destination chain's EVM chain ID**, not a CCIP selector. The entrypoint maps it to CCIP's own identifier internally.
* **`bridgeFeeToken` is the `0xEeee…EEeE` sentinel**, not the zero address. Passing `address(0)` reverts.

### Quote, then send the exact fee

```typescript theme={null}
const fee = await client.readContract({
  address: BRIDGE_ENTRYPOINT,
  abi: BRIDGE_ABI,
  functionName: 'previewFee',
  args: [shareAmount, data],
})

await walletClient.writeContract({
  address: BRIDGE_ENTRYPOINT,
  abi: BRIDGE_ABI,
  functionName: 'bridge',
  args: [shareAmount, data],
  value: fee, // exact, see below
})
```

<Warning>
  `msg.value` is compared for **equality** against the quoted fee. Overpaying reverts just like underpaying, so never pad the value. The quote is priced per block and moves with gas conditions, so quote and send in the same flow, and re-quote if the user delays.
</Warning>

### Route constraints

`messageGas` must fall inside the range configured for the destination route. Read it before transferring:

```typescript theme={null}
const ROUTE_ABI = parseAbi([
  'function selectorToChains(uint32 chainSelector) view returns (bool allowMessagesFrom, bool allowMessagesTo, address targetTeller, uint64 messageGasLimit, uint64 minimumMessageGas)',
])

const [, allowMessagesTo, targetTeller, messageGasLimit, minimumMessageGas] =
  await client.readContract({
    address: BRIDGE_ENTRYPOINT,
    abi: ROUTE_ABI,
    functionName: 'selectorToChains',
    args: [196],
  })
```

A route with `allowMessagesTo === false` or a zero `targetTeller` is not open, and the transfer will revert.

<Info>
  Cross-chain transfers are permissioned per wallet, the same as minting and redemption. An address that has not been enabled for transfers will revert even on an open route.
</Info>

### Depositing and transferring together

The transfer entrypoint also exposes `depositAndBridge`, which mints and transfers in one transaction. Because it consumes PAXG, it is callable **on Ethereum only**. Use it to put PAXGy on X Layer without a separate transfer step:

```typescript theme={null}
const DEPOSIT_AND_BRIDGE_ABI = parseAbi([
  'struct BridgeData { uint32 chainSelector; address destinationChainReceiver; address bridgeFeeToken; uint64 messageGas; bytes data; }',
  'function depositAndBridge(address depositAsset, uint256 depositAmount, uint256 minimumMint, BridgeData data) payable',
])
```

This path goes directly to the transfer entrypoint rather than the deposit entrypoint, so **no mint fee is charged**. Compute `minimumMint` from the gross deposit amount, not a post-fee amount. It takes no `to` argument: the PAXGy is issued to `destinationChainReceiver` on the destination chain.

### Settlement is not immediate

The source transaction confirming does not mean the PAXGy has arrived. CCIP finalizes on its own schedule; poll the destination balance rather than treating the source receipt as completion.

## What's next

<CardGroup cols={2}>
  <Card title="Overview" icon="circle-info" href="/v1.0.0/intro/products/paxgy/overview">
    What PAXGy is, how the share rate works, and who can access it.
  </Card>

  <Card title="PAXG" icon="coin" href="/v1.0.0/intro/products/paxg/overview">
    The gold-backed asset that underpins PAXGy.
  </Card>
</CardGroup>
