upKEEP

Docs

What upKEEP is, how conditions work, and exactly what it can do with your funds.

What is upKEEP?

upKEEP is a reusable financial automation layer for Arc. You describe a financial condition once, bind it to a single constrained action, and upKEEP keeps evaluating it against Arc Mainnet until it becomes true.

The mental model is one sentence:

When this financial condition becomes true, then perform this authorized action.

It is a condition engine rather than a payment scheduler. Conditional treasury automation is not a new idea; what upKEEP packages is the condition itself as infrastructure other Arc applications can build on.

The engine

A condition is a generic triple: a subject, an operator, and a threshold. Which evaluator interprets that triple is looked up by kind, so adding a new condition type means deploying one small view contract and registering it, not changing the registry or anything that holds funds.

Balance Guard is the first evaluator registered on that engine, not the product itself.

Evaluators are deliberately view-only: an evaluator can say a condition is true, but it cannot move money, write state or re-enter. Actions are deliberately not an open plugin point, because an action decides what upKEEP may do with your funds. Each new action type is added explicitly in the executor where it is auditable and visible to you.

How conditions work

A condition lives on-chain in the ConditionRegistry. Its predicate is evaluated by a view function on Arc, which is the single most important property of the design: the executor re-checks the predicate in the same transaction that moves the funds, so a keeper cannot fabricate a trigger.

The off-chain monitor is only a scheduler. It decides when to ask, never whether the answer is yes.

The state machine

After firing, a condition latches into a fired state and stays there until its evaluator confirms the subject genuinely recovered. This is what prevents a balance that simply stays below the threshold from executing on every polling cycle:

Threshold $5,000 · balance $4,900 → fires once.
Next poll, balance still $4,900 → does not fire.
Balance recovers above $5,000 (plus any re-arm buffer) → armed again.
Balance drops below $5,000 → fires again.

The optional re-arm buffer adds hysteresis. Without it, a balance oscillating by one wei around the threshold could fire repeatedly.

Permissions

upKEEP never takes custody of a private key and never receives an unlimited approval. Instead you deploy an AutomationVault, fund it with the amount you are willing to automate, and name exactly one destination and one per-execution ceiling.

From that point the executor can do precisely one thing:

Move at most maxPerExecution to exactly recipient, and only while the condition is true and armed.

It cannot change either value, cannot withdraw, cannot reach funds outside that vault, and cannot act at all once you pause or revoke. The worst case, a fully compromised executor, is bounded by one authorized amount per armed trigger, sent to an address you chose.

Withdrawal always works, including while the vault is paused or revoked. Your funds can never be stranded by a protocol-level action.

Arc Mainnet

upKEEP runs on Arc Mainnet. These values come from the official Arc documentation and are what this build is pointed at:

Network
Arc Mainnet
Chain ID
5042
RPC
https://rpc.mainnet.arc.io
Explorer
https://explorer.arc.io

USDC is the gas token

This is the detail that shapes the whole codebase. On Arc, USDC is the native gas token with 18 decimals, and it additionally exposes an ERC-20 interface at 0x3600000000000000000000000000000000000000 with 6 decimals over the same balance. To display a native value as USDC you divide by 1012.

Arc's documentation warns against recording balances from the 6-decimal view, because it truncates. upKEEP therefore accounts for everything in 18-decimal native wei and uses the 6-decimal view only for display.

The 20 Gwei floor

Arc's mempool silently drops transactions whose maxFeePerGas is under 20 Gwei: no receipt, no error, the transaction simply never appears in a block. Every write in the SDK reads the live gas price and clamps to that floor, so this cannot happen to you by accident.

Fees

No subscription, no setup fee, nothing to create a condition, nothing to monitor one. upKEEP charges 0.00% only on a successful automated execution.

$1,000 execution → upKEEP fee $0.50 → recipient receives $999.50

The fee is bounded at both ends so it can never become absurd:

  • A minimum of $0.001 per execution.
  • A hard ceiling of 1% of the transfer, which is what stops the minimum from swallowing a small amount. A $0.01 execution is charged $0.0001.

Every vault stores its fee rate in an immutable set at construction, so the rate you agreed to can never be raised afterwards by anyone, including protocol admins. The fee is always shown before you confirm and is never deducted silently.

Security

  • On-chain evaluation. The predicate is verified inside the same transaction that moves funds, so the keeper is a scheduler and not an oracle.
  • Triple replay protection. The registry latches the condition before any value moves, the executor records the derived execution id, and the vault independently refuses a spent id.
  • Checks-effects-interactions and reentrancy guards on every path that moves value, tested against a recipient that calls back in mid-payout.
  • Defence in depth on amounts. The ceiling is enforced at creation, again in the executor, and again in the vault.
  • No floating point in the money path. Every amount is a bigint of native wei, and the fee math is mirrored between Solidity and TypeScript against a shared vector table.
  • Your kill switch outranks everything. Pause and revoke are yours alone, and neither the protocol admin nor the keeper can undo them.

Example condition

IF USDC balance < $5,000
THEN transfer $1,000 USDC
TO the approved reserve wallet

For a first run on Mainnet, use a deliberately tiny amount: a $0.10 threshold and a $0.01 transfer proves the whole path end to end while risking almost nothing.

Create a condition

Using the SDK

This dashboard is a reference implementation. It calls the same @upkeep/sdk client any other Arc application would, with no private path around it.

import { createUpkeepClient } from '@upkeep/sdk';

const upkeep = createUpkeepClient({ addresses, walletClient });

const { vault } = await upkeep.vaults.create({
  recipient: reserveWallet,
  maxPerExecution: '1000',
  deposit: '5000',
});

const condition = await upkeep.conditions.create({
  wallet: treasury,
  type: 'BALANCE_BELOW',
  asset: 'USDC',
  threshold: '5000',
  vault,
  action: {
    type: 'TRANSFER_USDC',
    amount: '1000',
    recipient: reserveWallet,
  },
});

Amounts are strings or bigints of 18-decimal native wei. A JavaScript number is rejected, because it cannot represent every USDC amount exactly and money must never round by accident.

Authorization and crypto-agility

upKEEP implements no cryptography and makes no post-quantum security claim. What it does is avoid coupling a financial condition to any one signature scheme.

The engine is layered so authorization is replaceable:

Condition → Policy → Authorization → Execution → Arc Mainnet

A vault names the authorization scheme it uses. Replacing that scheme is a vault-level change: the condition keeps its id, threshold, action, history and re-arm state, and carries on running. You can migrate how automation is authorized without recreating what it does.

A provider can only restrict

The authorization check runs after the vault has already verified the caller, the amount, the recipient and the balance. A provider that approves changes nothing; only a refusal blocks. It fails closed if it reverts, and withdrawal never consults it, so funds can never be trapped behind one.

What Arc provides today

Arc runs an SLH-DSA-SHA2-128s verification precompile, live on Arc Mainnet. Post-quantum transaction signing is a future Arc milestone, so Arc accounts are not post-quantum secure today and upKEEP does not suggest otherwise.

upKEEP ships no provider that calls that precompile: Arc has not published its calldata encoding, and guessing the argument order of a signature verifier would be worse than waiting. The interface exists so adding it later is a registration rather than a rewrite.

What is not implemented

upKEEP is honest about its edges. In this version:

  • Two condition types are enabled — balance below and balance above — both running on the same deployed evaluator, plus one action type (transfer USDC).
  • Spend-rate, ratio and scheduled conditions are designed for but not built, and are marked “coming soon” wherever they appear.
  • Circle Agent Stack is not integrated. Its Agent Wallets support Arc testnet with mainnet listed as coming soon, so integrating it here would mean claiming Mainnet support that does not exist yet. An Agent Wallet can already be used as a monitored wallet today, since upKEEP watches any Arc address.
  • CCTP and Gateway are not used. The core flow is entirely within Arc, so adding them would be branding rather than function.
  • There is no notification backend and no hosted keeper.
upKEEP - Persistent financial conditions for Arc