The signedDocumentation Index
Fetch the complete documentation index at: https://docs.pfbridge.xyz/llms.txt
Use this file to discover all available pages before exploring further.
Order carries recipients as bytes32 so the same struct
works on both chains. But each chain still needs to derive a local
recipient out of those 32 bytes — and the two chains have different
rules for what a valid local address looks like. ProofBridge enforces
those rules at validateOrder time, before any balance or signature
work, so a malformed recipient fails fast and cheaply.
This matters to any integrator generating signed orders off-chain (JS
SDK, scripts, custom frontends): the encoding has to match the
destination chain, or the transaction reverts before it can do
anything useful.
EVM: upper 12 bytes must be zero
Anybytes32 that an EVM portal will cast to a local address — in
particular adRecipient on OrderPortal and orderRecipient on
AdManager — must be a left-padded 20-byte Ethereum address. The
library helper is AddressCast.assertEvmAddress (in
contracts/evm/src/libraries/AddressCast.sol):
0x1234...ABCD, the bytes32 field is
0x0000000000000000000000001234...ABCD — abi.encode(address) or
bytes32(uint256(uint160(addr))) both produce this layout.
Why this check exists
Without it, Solidity’saddress(uint160(uint256(b))) silently drops
the upper 12 bytes. A signer that accidentally stuffs a chain-specific
identifier into those bytes (e.g. padding with a Stellar pubkey
prefix) would see the transaction succeed but send funds to an
unexpected short address. The upper-bytes check makes that class of
error a hard revert instead of silent misdelivery.
Stellar: must decode to a G... account
On Stellar, the local recipients — ad_recipient on order-portal,
order_recipient on ad-manager — must decode to a Stellar
account address (Ed25519 pubkey, encoded as a G... strkey). The
primitive is bytes32_to_account_address in
proofbridge-core::token, which treats the 32 bytes as a raw Ed25519
pubkey and re-encodes it.
Soroban contract addresses (C... strkeys) are intentionally not
accepted as recipients. Contract addresses don’t come from an Ed25519
pubkey and can’t round-trip through this primitive, which keeps the
recipient surface narrow and predictable — funds always land with a
real account, never with an unknown contract.
All-zero input is rejected explicitly (RecipientZero /
InvalidAdRecipient) to prevent a forgotten-field bug from sending
funds into the black hole.
Integrator encoding: for a Stellar recipient
GABC...XYZ, the bytes32 field is the raw 32-byte Ed25519 public
key extracted from the strkey — not left-padded, not
prefix-tagged. Most Stellar SDKs expose this as rawPublicKey() or
similar on the Keypair.
Error surface
EVM
| Error | Raised by | Meaning |
|---|---|---|
AddressCast__NotEvmAddress(bytes32) | AddressCast.assertEvmAddress | Upper 12 bytes of the bytes32 are non-zero |
Stellar
| Error | Raised by | Meaning |
|---|---|---|
InvalidAdRecipient | order-portal::validate_order | Zero-bytes input passed as ad_recipient |
RecipientZero | ad-manager::validate_order | Zero-bytes input passed as order_recipient |
InvalidAccountAddress | proofbridge-core::token::bytes32_to_account_address | 32 bytes don’t decode to a valid Ed25519 pubkey |
Integrator impact
- Same
bytes32field, different encodings. When you build an order, thebridger/orderRecipient/adRecipient/adCreatorfields are allbytes32, but the encoding depends on the chain the field refers to:bridger+orderRecipient— encoded for the order chain.adCreator+adRecipient— encoded for the ad chain.
- Fail early, not mid-settlement. All recipient checks happen
inside
validateOrder, which is the first thing both portals do before any state change. Wrong encoding costs you gas but never partial settlement. - UIs should validate on input. Catching a malformed recipient at form-time is much cheaper than a failed on-chain call. The frontend’s order form enforces chain-appropriate address validation before letting the user submit.
bytes32s for both chains, and the
AddressCast library (contracts/evm/src/libraries/AddressCast.sol)
for the canonical Solidity helper.