For developers
Primcast is EVM-equivalent (Arbitrum Nitro), so your existing tools work: Foundry, Hardhat, viem, ethers and wagmi.
Endpoints and addresses
See Network for RPC, WebSocket, explorer and chain IDs, and for the live contract addresses. The deployment artifact contracts/deployments/<chainId>.json in the Primcast repository has the same data in machine-readable form.
ABIs
Build with forge build in contracts/, or use the generated portal/lib/abi.ts, which exports identityRegistryAbi, rwaTokenAbi, offeringAbi, assetHubAbi, rwaTokenFactoryAbi and systemRegistryAbi.
Check eligibility before acting
const ok = await client.readContract({
address: token, abi: rwaTokenAbi, functionName: "canReceive", args: [wallet, amount],
});
canReceive runs the full receiver check, including freeze, sanctions, KYC, accreditation, jurisdiction and holder cap, without reverting. Use IdentityRegistry.complianceOf(wallet) to explain why it fails.
Sending transactions
- Approvals: the settlement tokens are ordinary ERC-20s. Approve the
Offeringcontract beforesubscribe, and the token beforedistributeDividendorfundRedemptionPool. AssetHub needs approval for bonds. - Gas: transactions pay for L1 data as well as execution. Let the node estimate gas. With Foundry, always broadcast with
--skip-simulation --slow, otherwise the node rejects transactions withintrinsic gas too low. - Errors: contracts revert with typed errors:
Unauthorized,NotFound,AlreadyExists,LimitExceeded,InvalidArgument(string),InvalidStatus,Sanctioned(address),AccountFrozen(address),KYCRequired(address),AccreditedRequired(address),JurisdictionDenied(address),InsufficientBalance,PausedandZeroAmount.
Indexing
Every state change emits an event. There is no hidden state to reconstruct. The core events are listed at the end of each system's page (Identity, RWA token, Offerings, AssetHub). Ids are 1-indexed, and short strings such as jurisdictions, asset classes, units and device types are left-aligned bytes32.
Integrating a protocol
If your contract will hold RWA units (a pool, vault or market):
- Ask each token's admin to call
setVenue(yourContract, true). - If your contract can claim dividends and pass them to depositors, ask for
setVenueDividends(yourContract, true)and callclaimDividends()from your contract. - Expect transfers to ineligible users to revert, and surface
canReceivein your UI. - After maturity, your contract can still send units to eligible holders; design withdrawals around that.
Running keepers
Call Offering.processAll(n) (or process(ids) with ids from isDue) and RWAToken.checkMaturity(). Both are permissionless and never revert for items that have nothing due.
Local development
make local # anvil + deploy + smoke (+ DEX if checked out alongside)
make local-demo # seed demo issuers, investors, offerings, devices and readings
make portal-dev # this portal on http://localhost:3002 with dev wallets
The dev wallet offers the anvil accounts in the roles the demo seed gives them (authority, issuer, registrar, investors, meter operator, oracle). No private keys reach the browser.