2. Activate a contract account
A new contract has no activated Core account. In this tutorial you deploy a new contract, send it HYPE before activation, then activate it with a Core send of USDC and watch what changes.
You'll learn:
- Why a new contract's
core_user_existsis false, and what that blocks. - Where HYPE goes when you send it to an account that isn't activated: into an escrow that the precompiles can't see but the info API can.
- How a Core send of 1 USDC or more activates the account, who pays the 1 USDC fee, and how the escrow then moves to spot.
Prerequisites
| Item | Amount |
|---|---|
| HYPE on HyperEVM | 0.001 HYPE for the escrow step, plus less than 0.001 HYPE for gas |
| USDC in the spot balance of your Core account | 3 USDC: 2 USDC to the contract and 1 USDC fee |
HYPER_VYPER_TESTNET_KEY |
for the deploy, escrow and sweep steps |
HYPER_VYPER_CORE_FUNDER_KEY |
optional, for the activate step |
See testnet setup.
What activation changes
A contract that sends CoreWriter actions needs an activated Core account. If the account is not activated:
- Each CoreWriter action fails silently. The EVM transaction succeeds, but Core does nothing.
- HYPE and linked tokens sent to the account go into an escrow. They are not lost, but the contract cannot see or use them.
So a contract must check precompiles.core_user_exists(self) before it
sends an action.
The contract
# pragma version 0.4.3
# pragma evm-version cancun
# pragma optimize gas
# SPDX-License-Identifier: MIT
"""
@title Tutorial: activate the Core account of a contract
@notice A new contract has a HyperCore account that is not activated. This
contract shows the account before and after activation. It can also send
HYPE to Core before activation, to show the escrow.
@dev Tutorial code. Nobody has audited it.
"""
from hyper_vyper import bridge
from hyper_vyper import constants
from hyper_vyper import conversions
from hyper_vyper import core_writer
from hyper_vyper import precompiles
struct Status:
evm_block: uint256
core_exists: bool
spot_usdc: uint64 # Core USDC, 8 decimals
spot_hype: uint64 # Core HYPE, 8 decimals
OWNER: public(immutable(address))
@deploy
def __init__():
OWNER = msg.sender
@internal
@view
def _only_owner():
assert msg.sender == OWNER, "Only the owner"
@external
@view
def status() -> Status:
"""The precompile view of this account. It never shows escrow, and core_exists
stays false until activation."""
return Status(
evm_block=block.number,
core_exists=precompiles.core_user_exists(self),
spot_usdc=precompiles.spot_balance(self, constants.USDC_TOKEN_INDEX).total,
spot_hype=precompiles.spot_balance(self, conversions.hype_token_index()).total,
)
@external
@payable
def hype_to_core():
"""Send the HYPE of this call (18 decimals) to the Core account of this contract.
Before activation, Core holds it in an escrow. Activation moves it to spot."""
self._only_owner()
bridge.bridge_hype_to_core(msg.value)
@external
def sweep(token: uint64, amount_wei: uint64):
"""Send Core spot funds of this contract to the owner on Core (cleanup).
amount_wei is in Core wei: 8 decimals for USDC and for HYPE."""
self._only_owner()
assert precompiles.core_user_exists(self), "Core account not activated"
core_writer.send_asset(
OWNER, empty(address), constants.SPOT_DEX, constants.SPOT_DEX, token, amount_wei
)
statusreads the account through the precompiles.hype_to_coreusesbridge.bridge_hype_to_coreto send HYPE from EVM to the contract's Core account.sweepchecks activation first, then sends the Core funds back to the owner.
Steps
-
Deploy the contract. Run the step without
--executefirst to see the plan: -
Look at the new account:
-
Optional: send 0.001 HYPE to Core before activation. The step waits until the info API shows the escrow:
send 1000000000000000 (0.001 HYPE, 18 decimals) from EVM to the Core account of the contract plan: hype_to_core() value 1000000000000000 sent hype_to_core: 0x... in block N block N+5: exists False, precompile spot HYPE 0 info API escrow HYPE: 0.001 The HYPE is in escrow. The precompiles do not show it.The precompile shows 0 HYPE, while the info API shows 0.001 HYPE in escrow.
-
Activate the account with a Core send of 2 USDC from your Core account:
If
HYPER_VYPER_CORE_FUNDER_KEYis set, the step runstutorials/send_core.pyand then waits. If it is not set, the step prints the command. Run that command, then wait for the activation:uv run --script tutorials/send_core.py --to 0xCONTRACT --token USDC --amount 2 --execute uv run --locked tutorials/activate_account/run.py waitYour Core account pays the 1 USDC activation fee on top of the amount. Core transfer: 2 USDC from your Core account to 0x... block N: core_user_exists True account is activated contract 0x... at EVM block N+1 precompile core_user_exists: True precompile spot USDC: 200000000 (2 USDC, 8 decimals) precompile spot HYPE: 100000 (0.001 HYPE, 8 decimals) info API userRole: user info API spot USDC 2, spot HYPE 0.001 info API escrow (evmEscrows) HYPE: 0The contract has the full 2 USDC, and the escrowed HYPE is now on spot. Your Core account paid 3 USDC: 2 USDC to the contract and a 1 USDC fee. Check your own balance with the
spotClearinghouseStaterequest from testnet setup.
What can go wrong
- The activate step refuses less than 1 USDC. A smaller send does not
activate the account.
send_core.pyalso refuses to send HYPE to an account that is not activated. - Your Core account cannot pay. The send needs the amount plus 1 USDC, and
send_core.pychecks this before it sends. spotSendfails with "Action disabled when unified account is active". New user wallets are in unified account mode, sosend_core.pyusessendAsset, which works in that mode.- The escrow step refuses to send. After activation, HYPE goes to spot instead of escrow, so do the escrow step before the activate step.
- A contract action does nothing. Check
core_user_exists, because actions from an account that is not activated fail silently.
Clean up
Send the contract's Core USDC and HYPE back to your Core account: