diff --git a/docs/smart-contracts/views.md b/docs/smart-contracts/views.md
index 26951d86e..5683f5e77 100644
--- a/docs/smart-contracts/views.md
+++ b/docs/smart-contracts/views.md
@@ -2,86 +2,224 @@
title: Views
authors: 'Mathias Hiron (Nomadic Labs), Sasha Aldrick (TriliTech), Tim McMackin (TriliTech)'
last_update:
- date: 5 October 2023
+ date: 2 April 2024
---
-Views are a way for contracts to expose information to other contracts.
-
-A view is similar to an entrypoint, with a few differences:
-
-- Views return a value.
-- Contracts can call views and use the returned values immediately.
-In other words, calling a view doesn't produce a new operation.
-The call to the view runs immediately and the return value can be used in the next instruction.
-- Calling a view doesn't have any effect other than returning that value.
-In particular, it doesn't modify the storage of its contract and doesn't generate any operations.
-
-## Example View
-
-Here is an example that uses a view.
-The following contract is a ledger that handles a fungible token and keeps track of how many tokens are owned by each user.
-
-{
- Ledger contract
-
-
- Storage |
- Entrypoint effects |
-
-
-
-
-
-
- |
-
-
- view getBalance(user: address)
-
- - return
ledger[user].tokens
-
-
- transfer(nbTokens, destination)
-
- - Check that
tokens[caller].tokens >= nbTokens
- - Create an entry
tokens[destination] (value = 0 if it doesn't exist)
- - Add
nbTokens to tokens[destination].nbTokens
- - Subtract
nbTokens from tokens[caller].nbTokens
-
-
-
- |
-
-
-
}
-
-Another contract might provide an `equalizeWith` entrypoint such that if they have more tokens than another user, they can make their balances equal (plus or minus one if the total amount is odd).
-The following example code for this contract takes advantage of the `getBalance(user)` view of the first contract: to determine the balance of each user:
+Views are a way for contracts to expose information to other contracts and to off-chain consumers.
+
+Views help you get around a limitation in smart contracts: a smart contract can't access another contract's storage.
+Smart contracts can provide information via callbacks, but using a callback means calling entrypoints, which is an asynchronous action.
+
+By contrast, views are synchronous; a contract can call a view and use the information that it returns immediately.
+
+Like entrypoints, views can accept parameters, access the contract's storage, and call other views.
+Unlike entrypoints, views return a value directly to the caller.
+However, views can't cause side effects, so they can't create operations, including calling smart contracts and transferring tez.
+Views also can't change the contract storage.
+
+Off-chain users can run a view without creating a transaction, which is a convenient way to get information from a smart contract.
+For example, you can use the Octez client `run view` command to run a view from the command line.
+
+## Types of views
+
+Contracts can store the source code of their views either _on-chain_ or _off-chain_:
+
+ - The code of on-chain views is stored in the smart contract code itself, like entrypoints.
+ - The code of off-chain views is stored externally, usually in decentralized data storage such as IPFS.
+ The contract metadata has information about its off-chain views that consumers such as indexers and other dApps use to know what off-chain views are available and to run them.
+
+On-chain and off-chain views have the same capabilities and limitations.
+
+## Examples
+
+Views can provide information about tokens.
+You can use views to provide an account's balance of a token type or the total amount of a token in circulation.
+
+DEXs can provide the exchange rate between two tokens or the amount of liquidity in the pool.
+
+Instead of repeating certain logic in multiple places, you can put the logic in a view and use it from different smart contracts.
+
+## Creating views in JsLIGO
+
+Views in LIGO look like entrypoints because they receive the input values and storage as parameters, but they have the `@view` annotation instead of the `@entry` annotation.
+They return a value instead of a list of operations and the new value of the storage.
+
+This JsLIGO view returns the larger of two numbers:
+
+```ts
+type get_larger_input = [int, int];
+
+@view
+const get_larger = (input: get_larger_input, _s: storage): int => {
+ const [a, b] = input;
+ if (a > b) {
+ return a;
+ }
+ return b;
+}
+```
+
+This view returns a value from a big-map in storage:
+
+```ts
+type storageType = big_map;
+
+@view
+const get_balance = (key: string, s: storageType): string => {
+ const valOpt = Big_map.find_opt(key, s);
+ return match(valOpt) {
+ when(Some(val)): val;
+ when(None): "";
+ }
+}
+```
+
+## Calling views in JsLIGO
+
+This JsLIGO code calls the `get_larger` view from the previous example by passing the target contract address, parameters, and view name to the `Tezos.call_vew()` function:
+
+```ts
+@entry
+const callView = (_i: unit, _s: storage): return_type => {
+ const resultOpt: option = Tezos.call_view(
+ "get_larger", // Name of the view
+ [4, 5], // Parameters to pass
+ "KT1Uh4MjPoaiFbyJyv8TcsZVpsbE2fNm9VKX" as address // Address of the contract
+ );
+ return match(resultOpt) {
+ when (None):
+ failwith("Something went wrong");
+ when (Some(result)):
+ [list([]), result];
+ }
+}
+```
+
+If the view takes no parameters, pass a Unit type for the parameter:
+
+```ts
+const unitValue: unit = [];
+const resultOpt: option = Tezos.call_view(
+ "no_param_view", // Name of the view
+ unitValue, // No parameter
+ "KT1Uh4MjPoaiFbyJyv8TcsZVpsbE2fNm9VKX" as address // Address of the contract
+);
+```
+
+## Creating views in SmartPy
+
+Views in SmartPy look like entrypoints because they receive the `self` object and input values as parameters, but they have the `@sp.onchain_view` annotation instead of the `@sp.entrypoint` annotation.
+
+This SmartPy contract has a view that returns a value from a big-map in storage:
+
+```python
+@sp.module
+def main():
+
+ storage_type: type = sp.big_map[sp.address, sp.nat]
+
+ class MyContract(sp.Contract):
+ def __init__(self):
+ self.data = sp.big_map()
+ sp.cast(self.data, storage_type)
+
+ @sp.entrypoint
+ def add(self, addr, value):
+ currentVal = self.data.get(addr, default=0)
+ self.data = sp.update_map(addr, sp.Some(currentVal + value), self.data)
+
+ @sp.onchain_view
+ def getValue(self, addr):
+ return self.data.get(addr, default=0)
+
+@sp.add_test()
+def test():
+ scenario = sp.test_scenario("Callviews", main)
+ contract = main.MyContract()
+ scenario += contract
+
+ alice = sp.test_account("Alice")
+ bob = sp.test_account("Bob")
+
+ # Test the entrypoint
+ contract.add(addr = alice.address, value = 5)
+ contract.add(addr = alice.address, value = 5)
+ contract.add(addr = bob.address, value = 4)
+ scenario.verify(contract.data[alice.address] == 10)
+ scenario.verify(contract.data[bob.address] == 4)
+
+ # Test the view
+ scenario.verify(contract.getValue(alice.address) == 10)
+ scenario.verify(contract.getValue(bob.address) == 4)
+```
+
+## Calling views in SmartPy
+
+In SmartPy tests, you can call views in the contract just like you call entrypoints.
+However, due to a limitation in SmartPy, if the view accepts multiple parameters, you must pass those parameters in a record.
+For example, to call the `get_larger` view in the previous example, use this code:
+
+```python
+viewResult = contract.get_larger(sp.record(a = 4, b = 5))
+scenario.verify(viewResult == 5)
+```
+
+To call a view in an entrypoint, pass the view name, target contract address, parameters, and return type to the `sp.view()` function, as in this example:
+
+```python
+@sp.entrypoint
+def callView(self, a, b):
+ sp.cast(a, sp.int)
+ sp.cast(b, sp.int)
+ viewResponseOpt = sp.view(
+ "get_larger", # Name of the view
+ sp.address("KT1K6kivc91rZoDeCqEWjH8YqDn3iz6iEZkj"), # Address of the contract
+ sp.record(a=a, b=b), # Parameters to pass
+ sp.int # Return type of the view
+ )
+ if viewResponseOpt.is_some():
+ self.data.myval = viewResponseOpt.unwrap_some()
+```
+
+If the view takes no parameters, pass `()` for the parameter:
+
+```python
+viewResponseOpt = sp.view(
+ "no_param_view", # Name of the view
+ sp.address("KT1K6kivc91rZoDeCqEWjH8YqDn3iz6iEZkj"), # Address of the contract
+ (), # No parameter
+ sp.int # Return type of the view
+)
+```
+
+## Calling views with Taquito
+
+Calling a view with Taquito is similar to calling entrypoints.
+When you create an object to represent the contract, its `contractViews` property has a method for each view, which you can call as in this example:
```javascript
-equalizeWith(destination)
- destinationBalance = ledger.getBalance(destination)
- totalBalance = ledger.getBalance(caller) + destinationBalance
- targetBalance = totalBalance // 2
- ledger.transfer(targetBalance - destinationBalance, destination)
+const viewContractAddress = "KT1K6kivc91rZoDeCqEWjH8YqDn3iz6iEZkj";
+const contract = await Tezos.wallet.at(viewContractAddress);
+const result = await contract.contractViews.get_larger({a: 2, b: 12})
+ .executeView({ viewCaller: viewContractAddress });
+console.log(result);
```
+## Calling views with the Octez client
+
+To call a view with the Octez client, use the `run view` command, as in this example:
+
+```bash
+octez-client run view "get_larger" on contract "KT1Uh4MjPoaiFbyJyv8TcsZVpsbE2fNm9VKX" with input "Pair 4 5"
+```
+
+If the view takes no parameters, you can pass Unit or omit the `with input`.
+
+
## Implementation details
-- Michelson: [Operations on views](https://tezos.gitlab.io/active/michelson.html#operations-on-views)
+- Octez: [On-chain views](https://tezos.gitlab.io/active/views.html)
- Archetype: [View](https://archetype-lang.org/docs/reference/declarations/view)
- SmartPy: [Views in testing](https://smartpy.io/manual/scenarios/testing_contracts#views)
- LIGO: [On-chain views](https://ligolang.org/docs/protocol/hangzhou#on-chain-views)
+- Taquito: [On-chain views](https://tezostaquito.io/docs/on_chain_views)