Obtaining the user balance of a deployed FA2

The following question has been asked on Telegram:

I have deployed a nft token contract (using Fa2.fa2 template)
And now I’m writting a smartpy contract, nft marketplace and inside that code i want to fetch the balance (amount) using user address,
But currently I am not able to do so.

Solution 1: standard

You can get the balance by calling the balance_of entrypoint and giving a callback address on which the answer will be given.

:warning: Don’t forget to check that the sp.sender of the answer is the desired address and add avoid replay as anyone can call the fa2 and give your contract as the callback.

An example using the new syntax:

import smartpy as sp
import templates.fa2_lib_new as fa2_lib

@sp.module
def m():
    balance_of_request: type = sp.record(owner=sp.address, token_id=sp.nat).layout(
        ("owner", "token_id")
    )

    balance_of_requests: type = sp.list[balance_of_request]

    balance_of_response: type = sp.record(
        request=balance_of_request, balance=sp.nat
    ).layout(("request", "balance"))

    balance_of_responses: type = sp.list[balance_of_response]

    balance_of_param: type = sp.record(
        callback=sp.contract[balance_of_responses],
        requests=balance_of_requests,
    ).layout(("requests", "callback"))

    class Getter(sp.Contract):
        def __init__(self):
            self.data.balances = sp.big_map()

        @sp.entrypoint
        def getter(self, fa2_address, requests):
            contract = sp.contract(
                balance_of_param,
                fa2_address,
                "balance_of"
            ).unwrap_some(error="Fa2BalanceOfNotFound")
            param = sp.record(
                callback=sp.self_entrypoint("_setter"),
                requests=requests,
            )
            sp.transfer(param, sp.tez(0), contract)

        @sp.entrypoint
        def _setter(self, responses):
            sp.cast(responses, balance_of_responses)
            for response in responses:
                key = sp.record(
                    fa2=sp.sender,
                    token_id=response.request.token_id,
                    owner=response.request.owner)
                self.data.balances[key] = sp.record(
                    balance=response.balance,
                    now=sp.now)


if "templates" not in __name__:
    @sp.add_test(name="MyContract")
    def test():
        admin = sp.test_account("admin")
        alice = sp.test_account("alice")
        sc = sp.test_scenario([fa2_lib.t, fa2_lib.main, m])
        fa2 = fa2_lib.main.FungibleTestFull(
            administrator=admin.address,
            metadata=sp.big_map(),
            ledger={(alice.address, 0): 42},
            token_metadata=[sp.map()],
        )
        sc += fa2

        c1 = m.Getter()
        sc += c1
        c1.getter(
            fa2_address=fa2.address,
            requests=[sp.record(owner=alice.address, token_id=0)])
        sc.verify(c1.data.balances[
            sp.record(fa2=fa2.address, token_id=0, owner=alice.address)
        ] == sp.record(balance=42, now=sp.timestamp(0)))

Solution 2: non standard

If your FA2 contract inherited the mixin OnchainviewBalanceOf of the FA2 library, you can call the get_balance_of view. Contrary to the balance_of entrypoint, you get the result instantly and not after a callback.