> For the complete documentation index, see [llms.txt](https://docs.t-rex.network/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.t-rex.network/guides/identity/07.-link-a-wallet-to-an-onchainid.md).

# 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](/guides/identity/02.-deploy-an-onchainid.md)); 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

```mermaid
sequenceDiagram
    participant Wallet
    participant Owner as Identity Owner
    participant Identity as ONCHAINID
    participant Factory as IdentityFactory
    participant Gateway as ERC-7786 gateway

    alt the wallet is on the reference chain
        Owner->>Factory: nonceForAccount(account) (read)
        Wallet-->>Owner: signature over LinkAccount(account, identity, nonce, expiry)
        Owner->>Identity: instruct with a MANAGEMENT key
        Identity->>Factory: linkAccount(account, signature, nonce, expiry)
        Factory->>Factory: account valid and local, signature valid, nonce and expiry valid
        Factory->>Factory: wallet free, identity may link
        Factory-->>Owner: AccountLinked(account, identity, caller)
    else the wallet is on another chain
        Wallet->>Gateway: link proposal: wallet, identity, expiry
        Gateway->>Factory: deliver, sender authenticated
        Factory->>Factory: gateway trusted for that chain, sender is the wallet
        Factory-->>Owner: PendingCrossChainLinkProposed
        Owner->>Identity: instruct with a MANAGEMENT key
        Identity->>Factory: settlePendingCrossChainLink(account, true)
        Factory->>Factory: proposal not expired, wallet free
        Factory-->>Owner: CrossChainLinkConfirmed, AccountLinked
    end
```

## 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:**

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)`.

### 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:

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](/guides/claim-issuer/05.-issue-a-claim-to-an-investor.md)     |

## 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](/guides/agents/06.-register-an-investor-for-an-asset.md)).

> **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.

## Related flows

* [02 Deploy an ONCHAINID](/guides/identity/02.-deploy-an-onchainid.md) : links the first account
* [23 Revoke a wallet](/guides/identity/23.-revoke-a-wallet.md) : the terminal operation on a link
* [22 Add and remove keys on an ONCHAINID](/guides/identity/22.-add-and-remove-keys-on-an-onchainid.md) : keys are not links
* [06 Register an investor for an asset](/guides/agents/06.-register-an-investor-for-an-asset.md) : the local registration by an agent
* [24 Transfer through the identity](/guides/identity/24.-transfer-through-the-identity.md) : moving tokens held in a linked wallet
* [30 Delegate a position out to a satellite](/guides/cross-chain/30.-delegate-a-position-out-to-a-satellite.md) : uses a wallet linked on that satellite


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.t-rex.network/guides/identity/07.-link-a-wallet-to-an-onchainid.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
