Linking a token
Linking binds a HyperCore token to one ERC20 through the spot deployer's request and the contract deployer's finalisation; the protocol checks almost nothing else.
Linking tells HyperCore which ERC20 represents a spot token, and how their decimals differ. Until then a HyperCore spot token (HIP-1) and an ERC20 are independent, and anyone can deploy an ERC20 called “PURR”. No token can cross until it is linked.
The link is a handshake: the spot deployer names a contract and the contract’s deployer agrees, because the two may use different keys and deploy in either order.
The handshake in four steps, using PURR
Section titled “The handshake in four steps, using PURR”The docs’ example links PURR, token 1: 5 wei decimals on HyperCore and 18 on the HyperEVM, so evmExtraWeiDecimals is 18 minus 5, or 13.
1. Put the supply at the system address
Section titled “1. Put the supply at the system address”Each side’s system address must hold the whole balance that is not on that side. The docs’ sample script mints the maximum HyperCore supply to the system address; their PURR contract mints 600,000,000 PURR at 18 decimals to 0x2000000000000000000000000000000000000001 in its constructor.
2. Request the link (spot deployer)
Section titled “2. Request the link (spot deployer)”The spot deployer sends requestEvmContract, which the sample script signs as an L1 action. HyperCore stores the address as pending.
{ "type": "spotDeploy", "requestEvmContract": { "token": 1, "address": "0x<erc20 address, lowercase>", "evmExtraWeiDecimals": 13 }}3. Finalise the link (EVM deployer)
Section titled “3. Finalise the link (EVM deployer)”The contract’s deployer confirms with finalizeEvmContract, a top-level action not nested in spotDeploy. Its input says how the sender proves it deployed the contract; in the storage-slot cases the sender must match the stored address.
{"type": "finalizeEvmContract", "token": 1, "input": {"create": {"nonce": 0}}}{"type": "finalizeEvmContract", "token": 1, "input": "firstStorageSlot"}{"type": "finalizeEvmContract", "token": 1, "input": "customStorageSlot"}| Variant | When | What proves it |
|---|---|---|
create |
the contract was deployed from an EOA | the EOA sends the action with the nonce it used to deploy |
firstStorageSlot |
the contract was deployed by another contract, for example create2 from a multisig |
storage slot 0 of the contract holds the finaliser’s address |
customStorageSlot |
as above | the slot at keccak256("HyperCore deployer") holds the finaliser’s address |
4. The same finalisation from a contract
Section titled “4. The same finalisation from a contract”CoreWriter action 8 finalises from the HyperEVM: (uint64 token, uint8 encodedFinalizeEvmContractVariant, uint64 createNonce), with 1 Create, 2 FirstStorageSlot and 3 CustomStorageSlot; only Create reads createNonce. Since CoreWriter acts for msg.sender, a contract can finalise, which suits a slot that names a contract; the docs say no more. The contract must already exist on HyperCore (CoreWriter).
The numbers
Section titled “The numbers”| Parameter | Value |
|---|---|
evmExtraWeiDecimals range |
-2 to 18 inclusive |
| EVM decimals | weiDecimals + evmExtraWeiDecimals |
| Finalise variants | create (1), firstStorageSlot (2), customStorageSlot (3) |
| Custom slot | keccak256("HyperCore deployer") |
| Examples | PURR 5 to 18 (13); USDC 8 to 6 (-2); USDT0 8 to 6 (-2) |
| Action 8 on mainnet | 0 sightings in 3,000,001 EVM blocks |
Checking that a contract is really linked
Section titled “Checking that a contract is really linked”A contract calling itself a HyperCore token is that token only if HyperCore links it. Check evmContract.address in spotMeta, or the tokenInfo precompile at 0x80C, which also returns evmExtraWeiDecimals.
Circle’s USDC on the HyperEVM (0xb88339cb7199b77e23db6e890353e22632ba630f), used by lending markets, is not the contract HyperCore USDC is linked to (0x6b9e773128f453f5c2c60935ee2de2cbc5390a24, issuer not established). Moving between them is a swap (the two USDCs).
Choosing the offset
Section titled “Choosing the offset”Either side may be deployed first. After finalising, verify the link and offset with spotMeta or 0x80C.
Choose the offset for its rounding rule (moving assets):
- Positive (the HyperEVM is finer): HyperEVM to HyperCore remainders below
10^offsetare burned, under 1 wei per the docs. - Negative (coarser, as for USDC): HyperCore to HyperEVM amounts that are not a multiple of
10^|offset|are rejected, in five observed sends without exception, so every integrator must floor.
The sample script’s comment says linking “is not reversible”: treat the address and offset as permanent.
The protocol checks the handshake, not the token
Section titled “The protocol checks the handshake, not the token”| Checked | Not checked |
|---|---|
| The finaliser proves deployment, by create nonce or by the address stored in a slot | That the system address holds sufficient supply |
| The requested address is held as pending until a matching finalisation arrives | That the contract is a valid ERC20 |
What the contract’s transfer actually does |
Per the docs the linked contract may have arbitrary bytecode, so verify its source and total balance. HyperCore credits from its Transfer log and the HyperEVM side calls its transfer; a contract that lies in either breaks the one-supply property.
A link does not guarantee a working token. Mainnet USDC’s linked contract is an upgradeable transparent proxy (2,175 bytes) whose standard views reverted on two RPCs while transfers flow normally. Testnet USDC’s has the same shape, reverts even on transfer, and kept 99 USDC bridged to it stuck (measured).
Where the docs are out of date
Section titled “Where the docs are out of date”- The TypeScript-style interface spells the field
Address; the sample script sendsaddress, lowercasing the contract address. - The docs say mainnet PURR “will be linked” once linking is enabled on mainnet. That is out of date:
spotMetashows USDC and USDT0 linked on mainnet.
For where the spot deployer comes from, see deploying a spot token.