07. Link a wallet to an ONCHAINID
Linking a wallet binds an address to an identity in the global identity registry, so that every asset recognizes the address as belonging to that holder. The Identity Owner does it itself, once per wallet, with a proof from the wallet and a proof from the identity. A wallet on the reference chain proves control with a signature. A wallet on another chain proves it by sending a message from that chain. The link is permanent: a wallet belongs to one identity, forever.
At a glance
Triggered by
Identity Owner
Actors
Identity Owner, Investor, Platform
Contracts
IdentityFactory, ONCHAINID, an ERC-7786 gateway for a wallet on another chain
Preconditions
The identity exists (see 02); the owner controls a MANAGEMENT key of the identity; the owner controls the wallet; the wallet is not bound to any identity and was never revoked; the identity is not of a single-binding type; for a wallet on another chain, a gateway is trusted by the IdentityFactory for that chain
Result
The wallet is linked to the identity with the status Active, and resolves to it for every asset
Actors and roles
Identity Owner
Proves control of the wallet and has the identity request the link.
A MANAGEMENT key of the identity, and the key of the wallet
Investor
The Identity Owner in its capacity as holder. Its new wallet shares the identity's eligibility.
The identity's claims, unchanged
Platform
Builds the message to sign, relays the calls and verifies the result.
Whatever keys the owner has authorized
Sequence
Step by step
The flow has two paths. Steps 1 to 3 are for a wallet on the reference chain. Steps 4 and 5 are for a wallet on another chain, EVM or not.
Step 1. Prepare the link message
Who: Platform, on behalf of the Identity Owner
Call: Off-chain, using read calls
Before the call: The Platform knows the wallet and the identity.
What happens: The Platform encodes the wallet as an ERC-7930 interoperable address in canonical form, account. It reads IdentityFactory.nonceForAccount(account) and IdentityFactory.getAccountStatus(account), which must report that the wallet was never linked. It chooses an expiry, a timestamp in the near future. It builds the ERC-712 typed message LinkAccount(bytes account, address identity, uint256 nonce, uint256 expiry) in the domain of the IdentityFactory.
What changes: Nothing on-chain.
Step 2. Sign with the wallet
Who: Identity Owner, with the wallet's key
Call: Off-chain
Before the call: Step 1 is done.
What happens: The wallet signs the typed message. Its interface shows the four fields: the wallet, the identity it will belong to, the nonce and the expiry. An externally owned account signs with its key. A smart-contract wallet signs as it validates signatures under ERC-1271. A passkey or another ERC-7913 signer signs with its own scheme.
What changes: Nothing on-chain.
Step 3. Link from the identity
Who: Identity Owner (MANAGEMENT key), through the identity
Call: IdentityFactory.linkAccount(account, signature, nonce, expiry), called by the identity
Before the call: The signature of step 2 is available and not expired. A MANAGEMENT key instructs the identity to make the call, through a user operation or the identity's execution queue. An ACTION key cannot: the factory is a management-grade target.
What the contract checks:
accountis a well-formed interoperable address. Otherwise the call reverts on the encoding.The wallet is on an EVM chain and on this chain, with the chain written in its canonical form. Otherwise
NonEvmAccountorAccountNotOnLocalChain.For an ERC-7913 signer, its verifier is trusted by the factory. Otherwise
UntrustedVerifier.The current time is at most
expiry. OtherwiseExpiredSignature.The caller is an identity the factory created. Otherwise
NotFactoryIdentity.The signature is valid for the wallet over the message. Otherwise
InvalidSignature.The nonce is the wallet's current nonce. Otherwise the call reverts on the nonce.
The wallet is not itself an identity. Otherwise
CannotLinkFactoryIdentity.The identity is not of a single-binding type. Otherwise
CannotLinkToAssetIdentity.The wallet was never revoked, is not bound to another identity, and is not already linked to this one. Otherwise
WalletAlreadyRevoked,WalletBoundToAnotherIdentityorWalletAlreadyLinkedToIdentity.
What changes: The factory records the wallet as Active for the identity and consumes the nonce. It emits AccountLinked(account, identity, caller).
Step 4. Propose the link from the wallet's chain
Who: Identity Owner, with the wallet, on the wallet's own chain
Call: The wallet calls a function on its own chain to bind itself to the identity. The ERC-7786 gateway forwards the proposal to the IdentityFactory on the reference chain, with the wallet, the identity and an expiry. [[TBC: the contract and the function the wallet calls on its own chain]] This call proves that the caller controls the wallet. It does not prove that the caller owns the identity: the identity gives that second proof when it accepts the proposal, in the next step.
Before the call: A gateway is trusted by the IdentityFactory for the wallet's chain. The Platform reads IdentityFactory.isTrustedGateway(gateway, chainType, chainReference).
What the contract checks: When the message is delivered on the reference chain:
The gateway is trusted for the chain the message comes from. Otherwise the message is rejected.
The sender authenticated by the gateway is the wallet named in the proposal. Otherwise
CrossChainSenderWalletMismatch.The wallet is not on the reference chain. Otherwise
CrossChainLinkForLocalWallet.The proposal has not expired when it is delivered. Otherwise
PendingCrossChainLinkExpired.The identity was created by the factory, and the wallet has no entry yet. Otherwise
NotFactoryIdentityorWalletAlreadyHasEntry.
What changes: The factory stages the proposal for the wallet and emits PendingCrossChainLinkProposed. Nothing is linked yet.
Step 5. Accept the proposal from the identity
Who: Identity Owner (MANAGEMENT key), through the identity
Call: IdentityFactory.settlePendingCrossChainLink(account, true), called by the identity
Before the call: The Platform reads IdentityFactory.getPendingCrossChainLink(account), which returns the identity and the expiry of the proposal.
What the contract checks:
The caller is the identity named in the proposal. Otherwise
PendingCrossChainLinkIdentityMismatch.The proposal has not expired. Otherwise
PendingCrossChainLinkExpired.The same binding rules as in step 3, checks 9 and 10.
What changes: The factory links the wallet as Active, deletes the proposal, and emits CrossChainLinkConfirmed(account, identity) and AccountLinked. Calling with false declines: the proposal is deleted and CrossChainLinkRejected is emitted.
State after the flow
IdentityFactory.getIdentity(account)returns the identity.IdentityFactory.getAccountStatus(account)returnsActive.IdentityFactory.getAccounts(identity, start, end)includes the wallet, andgetAccountsCount(identity)has increased by one.For every asset,
TREXRegistry.isVerifiedfor the wallet now gives the identity's eligibility, with no registration by any Issuer.The wallet holds no key on the identity. Linking gives it no authority over the identity.
The identity can move tokens held in the wallet with
Token.identityTransfer.
Failure modes
Reverts on the address encoding
The interoperable address is malformed (step 3, check 1)
Encode the chain and the address in canonical ERC-7930 form
Reverts with NonEvmAccount or AccountNotOnLocalChain
The wallet is on another chain and the signature path was used, or the chain reference is padded (step 3, check 2)
For a wallet on another chain, use steps 4 and 5. Otherwise encode the chain in its canonical form
Reverts with UntrustedVerifier
The signer's verifier is not on the factory's list (step 3, check 3)
Use a signer whose verifier is trusted, or have Network governance vet the verifier
Reverts with ExpiredSignature
The expiry has passed, or was zero (step 3, check 4)
Sign again with a later expiry
Reverts with NotFactoryIdentity
The call did not come from an identity, for example it came from a key directly (step 3, check 5)
Have the identity make the call
Reverts with CannotLinkToAssetIdentity
The identity is of a single-binding type, such as the identity of a token (step 3, check 9)
Not possible for this identity
Reverts with InvalidSignature
The signature is not the wallet's, or was made over other fields (step 3, check 6)
Rebuild the message with the exact account, identity, nonce and expiry
Reverts on the nonce
The nonce was already used, or is not the current one (step 3, check 7)
Read nonceForAccount again and sign again
Reverts with WalletBoundToAnotherIdentity
The wallet already belongs to an identity (step 3, check 10)
A wallet cannot change identity. Use another wallet
Reverts with WalletAlreadyRevoked
The wallet was revoked (step 3, check 10)
A revoked wallet is never linked again. Use another wallet
The identity's instruction is refused
The key that instructed the identity holds ACTION only
Use a MANAGEMENT key
A cross-chain proposal never appears
The gateway is not trusted for the origin chain, or the sender is not the wallet (step 4, checks 1 and 2)
Use a trusted gateway, and send from the wallet itself
Reverts with PendingCrossChainLinkExpired
The proposal expired before it was delivered or accepted (step 4, check 4, and step 5, check 2)
Decline it with false to clear it, then propose again
The wallet is linked and still cannot receive an asset
The identity lacks a valid claim for a required topic
Linking is not eligibility. See 05
Common misunderstandings
Wrong: "The Issuer registers the investor's wallet."
Right: The Identity Owner links its own wallets, with a proof from the wallet and a proof from the identity. No Issuer is involved, and the link holds for every asset. An agent can still register an address for one asset, which is a separate, local operation (06).
Wrong: "Linking a wallet whitelists it."
Right: A link says whose the wallet is. Eligibility is evaluated on the identity's claims, at each transfer. A linked wallet of an identity with no valid claims cannot receive.
Wrong: "A wallet can be unlinked and linked to another identity."
Right: A binding is permanent. A wallet can be revoked, which is terminal: it keeps resolving to its identity for attribution and can never be linked again, to any identity.
Wrong: "A linked wallet can act for the identity."
Right: Links and keys are separate. A linked wallet has no key on the identity. It is the identity that has authority over its linked wallets, not the reverse.
Integration notes
Show the Identity Owner the four fields of the typed message. The link is permanent, and the signature is what creates it.
Keep expiries short. An unused signature stays usable until it expires.
Read
getAccountStatus(account)before starting. A wallet that was ever linked or revoked cannot be linked.The same address on two EVM chains is two wallets. Link the one on the reference chain with a signature, and the one on a satellite with a message from that satellite.
The identity's call is a management operation. In an ERC-4337 flow, the user operation is signed by a
MANAGEMENTkey and can be sponsored by a paymaster.Index
AccountLinked,AccountRevoked,PendingCrossChainLinkProposed,CrossChainLinkConfirmedandCrossChainLinkRejectedto maintain the wallets of each identity.An identity needs no link to hold tokens at its own address. Linking is for other addresses.
Related flows
02 Deploy an ONCHAINID : links the first account
23 Revoke a wallet : the terminal operation on a link
22 Add and remove keys on an ONCHAINID : keys are not links
06 Register an investor for an asset : the local registration by an agent
24 Transfer through the identity : moving tokens held in a linked wallet
30 Delegate a position out to a satellite : uses a wallet linked on that satellite
Last updated