# (Old) Metaplay Internal Immutable X Wallet Binding "RFC"

This document is a (slightly cleaned up) document that we used internally back when planning the current Web3/NFT integration. The topic of the document is specifically binding an Immutable X (and Ethereum) account to a player account. This was written before the bulk of implementation work actually began, and thus this document does not precisely reflect the current implementation, but this may be still be helpful in illustrating some parts more concretely (there were no significant conceptual changes from this proposal).

Some Metaplay-specific jargon can be seen in this document, in particular:

- "Social platform" = a general concept in Metaplay for a third-party platform which may be used for user authentication. Originally used for "login via Facebook" and such (thus the word "social")

## Definitions

- **Ethereum** (ETH) is one of the big blockchains.
- **Ethereum Account** is identified by its its **Address.** The address is a hash of a **Public Key**.
- **Ethereum Wallet** is the software to manage a *Ethereum Accounts*.
- **Immutable X** (IMX) is an off-chain platform facilitating asset trading. It performs operations off-chain and flushes aggregate summaries of them periodically into **Ethereum**.
- **Immutable X User** is identified by its **Public Key**, also known as the **StarkKey** and as an **Account**.

## Overview

This document describes the proposed mechanism for binding *Immutable X* and *Ethereum* accounts to Game Accounts. The binding mechanism should allow for Game Server to reliably identify the NFT assets owned by the player and allow the player to log in with their Immutable X or Ethereum account.

This proposal is split to two parts. First to securely logging in with an Immutable X account, using social authentication mechanism, and then accessing wallet contents.

## Social Login with Immutable X

The proposal is to represent Immutable X and Ethereum Accounts as social platforms. The login flow would be:

1. User clicks on *Login with Crypto wallet / Metamask* button

2. Web browser either creates or logs in into an IMX account. This results in client claiming to own a certain ETH account and IMX account. We use basic LinkSDK here.

    ```jsx
    // Try to get from previous invocation
    var ethAddress = localStorage.getItem('ETH_WALLET_ADDRESS')
    var starkAddress = localStorage.getItem('STARK_PUBLIC_KEY')
    if (ethAddress !== null || starkAddress !== null)
    {
        return {ethAddress, starkAddress}
    }

    // Login into Immutable X
    var setup;
    try {
        setup = await link.setup({})
    } catch (error) {
        // return error
    }

    localStorage.setItem('ETH_WALLET_ADDRESS', setup.ethAddress);
    localStorage.setItem('STARK_PUBLIC_KEY', setup.starkPublicKey);
    return {ethAddress, starkAddress}
    ```

3. Server generates a challenge to prove ownership of the ETH wallet. IMX account is not yet validated. Client signs the challenge with its ETH private key in [Personal_sign](https://geth.ethereum.org/docs/rpc/ns-personal#personal_sign) mode. As the ETH key is not tied to any project, we limit the reuse of the signature with timestamp and prevent forging with a MAC.

    ```jsx
    // Server generates the following challenge
    Timestamp = now().Format()
    Message =
    """
    Use your crypto wallet in [Game Name].

    Signing this message binds the wallet into the game. This does not cost you any gas.

    # Receipt
    player_id: Player:XXXX
    eth_wallet: 123123123
    immutablex_wallet: 123123123
    timestamp: 2022-01-01 12:21:12
    signature: [HMAC of this message (without this signature)]
    """

    Description =
    """
    Sign in into [Game Name].
    """

    // Client signs the message
    let signature = await link.sign({"message": Message, "description": Description})
    // signature.result is now the sign(keccak256(Prefix . Message), privateKey)

    // ..and passes them to client
    make_claim(playerid, ethAddress, starkAddress, timestamp, signature) 
    ```

4. Server verifies the claim. Server recreates the challenge from the values given by the client and verifies the ETH signature. The challenge-response validation is hence stateless on the server-side.
    
    <aside>
    ⚠️ Since the claim is HMAC’d, we can accept the claim parameters (timestamp, playerid, etc..) back from client without validation beyond well-formedness checks, and without server-side bookkeeping. Client is unable to mutate them as HMAC wouldn’t match mutated values, causing it to sign a different message ⇒ inevitably causing signature check to fail.
    
    </aside>
    
    The claim verification proves to the server the client’s control over the ETH private key. We then find the registered IMX accounts matching the ETH address.
    

    ```jsx
    verify_immutablex(playerid, eth_address, imxAddress, timestamp, eth_signature)
    assert timestamp in [now - 15min, now]

    // Hash message with BouncyCastle Keccak here.
    // If signatures match, client has proven the ownership of the Ethereum wallet.
    var message = generate_message(timestamp, player_id, eth_address, imxAddress)
    assert check_signature(bc_keccak(signedMessagePrefix + message), eth_address, eth_signature)

    // Next, let's check the StarkKey is attached to the Ethereum wallet
    var response = await { GET https://api.[*platform].x.immutable.com/v1/users/0x{eth_address} }
    assert immutablex_wallet in response.accounts

    // Use all accounts as social platforms:
    resolvedSocialKeys:
    SocialKey(Ethereum, eth_address),
    SocialKey(ImmutableX, response.accounts[])..,
    ```

## Accessing Wallet contents

The social authentication system will make sure an IMX/ETH may only be bound to one player. Hence, we can mirror into player state the chain states of the social accounts.

**Note:** this is only a simplified sketch of how the server actually manages NFTs in game.

```jsx
class ImmutableXCollectionBase
{
    public abstract string CollectionId { get; }
}
class MySuperHeroesCollection : ImmutableXCollectionBase
{
    CollectionId => "coco"
}
class PlayerModel
{
   [MetaMember(123)] MySuperHeroesCollection Heroes;
}

// Login or actor init refresh ownership. Third party API requests may be slow so don't block anything.

foreach (ImmutableXCollectionBase collection in PlayerModel)
{
    RunInBackground(
        newContents = await GetCollectionContents(collection)
        if (collection.Contents != newContents)
            RunOnForeground(UpdateContentsAction(newContents))
    }
}

async Task<> GetCollectionContents(ImmutableXCollectionBase collection)
{
    List<Asset> assets = new List<>();
    foreach (var ethAccount in model.AttachedAuthMethods.Keys.Where(kv => kv.Platform == AuthenticationPlatform.Ethereum))
    {
        var response = (await GET https://api.[*platfotm].x.immutable.com/v1/assets?user={ethAccount.Key}&collection={collection.CollectionId})
        ParseAssets(response.result, assets)

        while (response.remaining != 0)
        {
            response = (await GET https://api.[*platfotm].x.immutable.com/v1/assets?user={ethAccount.Key}&collection={collection.CollectionId}&cursor={response.cursor})
            ParseAssets(response.result, assets)
        }
    }
    return assets
}
```