A Peer.xyz / zkp2p V3 post-intent hook that bridges the deposit token from Base to a SKALE chain through SKALE IMA immediately after an intent is fulfilled on zkp2p.
Post-intent hooks run during fulfillIntent on the OrchestratorV2, after the
payment proof has been verified and all fees have been deducted. The flow:
User → OrchestratorV2 (Base) ─── fulfillIntent ──▶ SkaleBridgeHook (Base)
│
│ depositERC20Direct
▼
DepositBoxERC20 (Base)
│
│ IMA message
▼
SKALE chain → recipient
At fulfill time the Orchestrator:
- Computes
executableAmount = releaseAmount − fees. - Approves the hook to pull exactly
executableAmountof the deposit token. - Calls
hook.execute(ctx, fulfillHookData). - Verifies the hook pulled exactly
executableAmountand did not send tokens back to the Orchestrator, then resets the allowance to0.
SkaleBridgeHook uses that allowance to pull the tokens from the Orchestrator,
approves the IMA DepositBoxERC20, and calls depositERC20Direct to deliver
them to ctx.intent.to on the configured SKALE chain.
See docs.peer.xyz — Post-intent hooks for the full hook lifecycle and invariants.
Implements IPostIntentHookV2 and ISkaleBridgeHook.
Constructor parameters:
| Name | Description |
|---|---|
_orchestrator |
zkp2p OrchestratorV2 address on the source chain (Base) |
_depositBox |
SKALE IMA DepositBoxERC20 address on the source chain |
_messageProxy |
SKALE IMA MessageProxyForMainnet address on the source chain |
_skaleChainName |
Destination SKALE chain name (verified via isConnectedChain at deploy) |
Owner-only configuration:
addTokenToWhitelist(address token)— mark a token as bridgeable by this hook.removeTokenFromWhitelist(address token)— revoke a token.
A deposit is allowed if the token is whitelisted on the DepositBoxERC20 for
the destination SKALE chain or whitelisted locally on this hook.
contracts/ Solidity sources (Hardhat + Foundry)
SkaleBridgeHook.sol
interfaces/
mocks/ Test-only mock contracts
script/ Foundry deploy scripts
migrations/ Hardhat deploy scripts (TypeScript)
test/ Hardhat/Mocha tests (TypeScript)
utils.ts Typed deploy helpers shared across test files
foundry-test/ Foundry tests (Solidity)
Both toolchains compile the same source tree but write artifacts to separate folders so they never clobber each other:
| Tool | Artifacts | Cache |
|---|---|---|
| Hardhat | artifacts/ |
cache/ |
| Foundry | forge-out/ |
forge-cache/ |
# Node / Hardhat toolchain
yarn install
# Foundry toolchain (installs forge-std into lib/)
forge install foundry-rs/forge-std --no-gitCopy .env.example to .env and fill in the deployment variables.
# Hardhat
yarn compile
# Foundry
forge buildyarn lint # solhint
forge fmt --checkyarn testRuns TypeScript compilation then the full Mocha suite via Hardhat. Coverage report is generated with:
yarn hardhat coverageforge test -vvvDeploy-script tests live in foundry-test/ and are executed as part of the
normal forge test run — no separate step is needed.
Set environment variables (see .env.example):
| Variable | Description |
|---|---|
PRIVATE_KEY |
Deployer private key |
ENDPOINT |
RPC URL of the source chain (Base / Base Sepolia) |
ORCHESTRATOR |
OrchestratorV2 address (0x888888359E981B5225CA48fbCdCeff702FC3b888 on Base) |
DEPOSIT_BOX |
DepositBoxERC20 address on the source chain |
MESSAGE_PROXY |
MessageProxyForMainnet address on the source chain |
SKALE_CHAIN_NAME |
Destination SKALE chain name |
OWNER |
(optional) Address to transfer ownership (2-step) to after deploy |
yarn hardhat run migrations/deploySkaleBridgeHook.ts --network customforge script script/DeploySkaleBridgeHook.s.sol:DeploySkaleBridgeHook \
--rpc-url "$ENDPOINT" \
--private-key "$PRIVATE_KEY" \
--broadcast \
-vvvvPass the deployed hook address as postIntentHook when signaling an intent:
await orchestrator.signalIntent({
escrow,
depositId,
amount,
to, // default recipient on SKALE
paymentMethod,
fiatCurrency,
conversionRate,
referralFees: [],
gatingServiceSignature,
signatureExpiration,
postIntentHook: skaleBridgeHookAddress,
preIntentHookData: "0x",
data: "0x", // signalHookData (unused by this hook)
});At fulfill time, this hook always bridges to ctx.intent.to.
| Contract | Network | Address |
|---|---|---|
| OrchestratorV2 (zkp2p) | Base | 0x888888359E981B5225CA48fbCdCeff702FC3b888 |
| EscrowV2 (zkp2p) | Base | 0x777777779d229cdF3110e9de47943791c26300Ef |
IMA MessageProxyForMainnet |
Base | 0x2C3cae1A2143De33F7fe887ad0428d33BBBd0A62 |
IMA MessageProxyForMainnet |
Base Sepolia | 0xA1e244C6cE94FF2bb5f4533783FBc44D1f190045 |
IMA DepositBoxERC20 |
Base | 0x7f54e52D08C911eAbB4fDF00Ad36ccf07F867F61 |
IMA DepositBoxERC20 |
Base Sepolia | 0x6722D0f037A461a568155EA0490753E9C8825FC9 |
Always re-verify the IMA and zkp2p addresses against their official docs before deploying.
MIT
Post-intent hook for Peer.xyz/zkp2p that automatically bridges USDC from Base to SKALE Base when users onramp.
User → zkp2p (Base) → SkaleBridgeHook → IMA Message → SkaleReceiver (SKALE Base) → User
- Post-intent hook implementing
IPostIntentHookV2 - Receives USDC from zkp2p Orchestrator after intent fulfillment
- Sends IMA message to SKALE Base
- Locks tokens for liquidity-based bridging
- Receives IMA messages from Base
- Releases mapped USDC to recipients
- Must be registered with SKALE's MessageProxy
# Install dependencies
# Deploy to Base Sepolia
# Deploy to SKALE Base Sepolia
# Deploy to Base
# Deploy to SKALE Base
After deploying SkaleReceiver, register it with SKALE's MessageProxy:
cast send 0xd2AAa00100000000000000000000000000000000 \
"grantRole(bytes32,address)" \
0x96e3fc3be15159903e053027cff8a23f39a990e0194abcd8ac1cf1b355b8b93c \
<RECEIVER_ADDRESS> \
--rpc-url <SKALE_RPC>import { OrchestratorV2 } from "@peerxyz/orchestrator";
await orchestrator.signalIntent({
escrow: escrowAddress,
depositId: depositId,
amount: amount,
to: recipientAddress,
paymentMethod: keccak256("card"),
postIntentHook: hookAddress, // SkaleBridgeHook address
data: "0x" // Can pass arbitrary bytes, but currently unused by Skale
});The SkaleReceiver contract needs to be funded with mapped USDC to release to recipients:
| Network | USDC |
|---|---|
| Mainnet | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Sepolia | 0x03655B2e3C0C3D9a5bB79b5451ea47eFCDbd3AAF |
| Network | Chain ID | USDC |
|---|---|---|
| Mainnet | 1187947933 | TBD |
| Sepolia | 324705682 | 0x2e08028E3C4c2356572E096d8EF835cD5C6030bD |
| Contract | Address |
|---|---|
| OrchestratorV2 (Base) | 0x888888359E981B5225CA48fbCdCeff702FC3b888 |
| MessageProxy (SKALE) | 0xd2AAa00100000000000000000000000000000000 |
SKALE's native DepositBox is designed for Ethereum → SKALE bridging. For Base → SKALE, this implementation uses:
- IMA Messaging: Cross-chain communication between Base and SKALE Base
- Liquidity Model: Tokens locked on Base, released from pre-funded liquidity on SKALE Base
- Verify if MessageProxy exists on Base for direct IMA communication
- Find actual DepositBox address on Base (if exists)
- Confirm SKALE Base mainnet USDC address
- Set up automated liquidity management for SkaleReceiver
MIT