For the complete documentation index, see llms.txt. This page is also available as Markdown.

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

Actor
Role in this flow
What they hold

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.

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.

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:

  1. account is a well-formed interoperable address. Otherwise the call reverts on the encoding.

  2. The wallet is on an EVM chain and on this chain, with the chain written in its canonical form. Otherwise NonEvmAccount or AccountNotOnLocalChain.

  3. For an ERC-7913 signer, its verifier is trusted by the factory. Otherwise UntrustedVerifier.

  4. The current time is at most expiry. Otherwise ExpiredSignature.

  5. The caller is an identity the factory created. Otherwise NotFactoryIdentity.

  6. The signature is valid for the wallet over the message. Otherwise InvalidSignature.

  7. The nonce is the wallet's current nonce. Otherwise the call reverts on the nonce.

  8. The wallet is not itself an identity. Otherwise CannotLinkFactoryIdentity.

  9. The identity is not of a single-binding type. Otherwise CannotLinkToAssetIdentity.

  10. The wallet was never revoked, is not bound to another identity, and is not already linked to this one. Otherwise WalletAlreadyRevoked, WalletBoundToAnotherIdentity or WalletAlreadyLinkedToIdentity.

What changes: The factory records the wallet as Active for the identity and consumes the nonce. It emits AccountLinked(account, identity, caller).

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:

  1. The gateway is trusted for the chain the message comes from. Otherwise the message is rejected.

  2. The sender authenticated by the gateway is the wallet named in the proposal. Otherwise CrossChainSenderWalletMismatch.

  3. The wallet is not on the reference chain. Otherwise CrossChainLinkForLocalWallet.

  4. The proposal has not expired when it is delivered. Otherwise PendingCrossChainLinkExpired.

  5. The identity was created by the factory, and the wallet has no entry yet. Otherwise NotFactoryIdentity or WalletAlreadyHasEntry.

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:

  1. The caller is the identity named in the proposal. Otherwise PendingCrossChainLinkIdentityMismatch.

  2. The proposal has not expired. Otherwise PendingCrossChainLinkExpired.

  3. 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) returns Active.

  • IdentityFactory.getAccounts(identity, start, end) includes the wallet, and getAccountsCount(identity) has increased by one.

  • For every asset, TREXRegistry.isVerified for 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

Symptom
Cause
Fix

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 MANAGEMENT key and can be sponsored by a paymaster.

  • Index AccountLinked, AccountRevoked, PendingCrossChainLinkProposed, CrossChainLinkConfirmed and CrossChainLinkRejected to maintain the wallets of each identity.

  • An identity needs no link to hold tokens at its own address. Linking is for other addresses.

Last updated