Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 MedFlow

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
41 changes: 19 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NumscriptEx
NumscriptEx is a library that allows its users to check and run Numscripts in Elixir. And if you don't know what are
Numscripts, here a quick explanation:
NumscriptEx is a library that allows its users to check and run Numscripts in Elixir. If this is your first time hearing about
Numscripts, here is a quick explanation:

[Numscript](https://docs.formance.com/numscript/) is a DSL made by [Formance](https://www.formance.com/)
that simplifies complex financial transactions with scripts that are easy to read,
Expand All @@ -20,11 +20,11 @@ end
```
### Configuration
NumscriptEx needs some external assets ([Numscript-WASM](https://github.com/PagoPlus/numscript-wasm)),
and you can configure said assets if you want.
and you can override the default version.

Available configurations:
- `:version`: is the release version to be downloaded;
- `:retries`: number of download retries in case of failure.
- `:version` the binary release version to use (see [numscript-wasm releases](https://github.com/PagoPlus/numscript-wasm/releases/)).
- `:retries` number of times to retry the download in case of a network failure.

Ex:
```elixir
Expand All @@ -47,7 +47,7 @@ A numscript needs some other data aside the script itself to run correctly, and
`Numscriptex.Run` solves this problem.

If you want to know what exactly these additional data are, you can see the
[numscript playground](https://playground.numscript.org/?template=simple-send) for examples.
[Numscript Playground](https://playground.numscript.org/?template=simple-send) for examples.

The abstraction is made by creating a struct:
```elixir
Expand All @@ -58,27 +58,23 @@ iex> %Numscriptex.Run{
...> }
```
Where:
- `balances`: are a map with the account's assets balances;
- `metadata`: metada variables;
- `variables`: variables used inside the script.
- `:balances` a map with the account's assets balances.
- `:metadata` [metada variables](https://docs.formance.com/numscript/reference/metadata);
- `:variables` [variables](https://docs.formance.com/numscript/reference/variables) used inside the script.

You can read more about Metadata clicking [here](https://docs.formance.com/numscript/reference/metadata)
and more about Variables clicking [here](https://docs.formance.com/numscript/reference/variables)

And to create a new struct, you can use the `put/3` or `put!/3` functions. Ex:
And to create a new struct, you can use the `Numscriptex.Run.put/3` or `Numscriptex.Run.put!/3` functions. Ex:
```elixir
iex> variables = %{"order" => "orders:2345"}
...> balances = %{"orders:2345" => %{"USD/2" => 1000}}
...>
...> metadata = %{
...> "merchants:1234" => %{"commission" => "15%"},
...> "orders:2345" => %{"merchant" => "merchants:1234"}
...> }
...>
...> Numscriptex.Run.new()
...> |> Numscriptex.Run.put!(:balances, balances)
...> |> Numscriptex.Run.put!(:metadata, metadata)
...> |> Numscriptex.Run.put!(:variables, variables)
...> |> Numscriptex.Run.put!(:balances, balances)
...> |> Numscriptex.Run.put!(:metadata, metadata)
...> |> Numscriptex.Run.put!(:variables, variables)
```

Will return:
Expand All @@ -97,7 +93,7 @@ iex> %Numscriptex.Run{
Kindly reminder: you will always need a valid `Numscriptex.Run` struct to successfully execute your scripts.

### Check
To use `check/1` you just have to pass your numscript as it's argument. Ex:
To use `Numscriptex.check/1` you just have to pass your numscript as it's argument. Ex:

```elixir
iex> "tmp/script.num"
Expand Down Expand Up @@ -140,10 +136,9 @@ iex> {:ok, %{
...> }
...> }
```
`:script` is the only key that will always return if your script is valid, the
other three are optional.
The `:script` is the only field that will always return if your script is valid, the other three are optional.
### Run
To use `run/2` your first argument must be your script (the same you used in `check/1`), and the second must be the `%Numscriptex.Run{}` struct. Ex:
To use `Numscriptex.run/2` your first argument must be your script (the same you used in `Numscriptex.check/1`), and the second must be the `%Numscriptex.Run{}` struct. Ex:

```elixir
iex> Numscriptex.run(script, struct)
Expand Down Expand Up @@ -187,4 +182,6 @@ iex> %{
```

## License
TODO
Copyright (c) 2025 MedFlow

This library is MIT licensed. See the [LICENSE](https://github.com/PagoPlus/numscriptex/blob/main/README.md) for details.
16 changes: 7 additions & 9 deletions lib/numscriptex.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule Numscriptex do
@moduledoc """
NumscriptEx is a library that allows its users to check and run [numscripts](https://docs.formance.com/numscript/)
NumscriptEx is a library that allows its users to check and run [Numscripts](https://docs.formance.com/numscript/)
via Elixir.

Want to check if your script is valid and ready to go? Use the `check/1` function.
Expand All @@ -14,6 +14,8 @@ defmodule Numscriptex do

require AssetsManager

AssetsManager.ensure_wasm_binary_is_valid()

@type check_log() :: CheckLog.t()

@type check_result() :: %{
Expand Down Expand Up @@ -49,12 +51,8 @@ defmodule Numscriptex do
optional(:details) => any()
}

AssetsManager.ensure_wasm_binary_is_valid()

@doc """
`version/0` simply shows a map with both Numscript-WASM and NumscriptEx versions.

Ex:
`version/0` simply shows a map with both [Numscript-WASM](https://github.com/PagoPlus/numscript-wasm) and NumscriptEx versions. Ex:

```elixir
iex> Numscriptex.version()
Expand Down Expand Up @@ -83,7 +81,7 @@ defmodule Numscriptex do
Ex:

```elixir
iex> script = "send [USD/2 100] ( source = @foo destination = @bar)"
iex> script = "send [USD/2 100] (source = @foo destination = @bar)"
iex> Numscriptex.check(script)
{:ok, %{script: script}}
```
Expand All @@ -106,11 +104,11 @@ defmodule Numscriptex do

@doc """
To use `run/2` your first argument must be your script, and the second must
be a `%Numscriptex.Run{}` (go to Numscriptex.Run module to see more) struct.
be a `%Numscriptex.Run{}` (go to `Numscriptex.Run` module to see more) struct.
Ex:

```elixir
iex> script = "send [USD/2 100] ( source = @foo destination = @bar)"
iex> script = "send [USD/2 100] (source = @foo destination = @bar)"
...> balances = %{"foo" => %{"USD/2" => 500, "EUR/2" => 300}}
...>
...> struct =
Expand Down
10 changes: 5 additions & 5 deletions lib/numscriptex/balances.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
defmodule Numscriptex.Balances do
@moduledoc """
`Numscriptex.Balances` is responsible for building the account's final balance
after running your [numscript](https://docs.formance.com/numscript/), so you
after running your [Numscript](https://docs.formance.com/numscript/), so you
can see the results of all transactions.
"""

@doc """
Receives the account assets (balance field from `%Numscriptex.Run{}`), and the
postings that are generated after running the numscript transaction.
Receives the account assets (balance field from `%Numscriptex.Run{}`), and the
postings that are generated after running the numscript transaction.

The result will be a map contaning the initial and final balances of each
account assets. Ex:
Expand All @@ -28,7 +28,7 @@ defmodule Numscriptex.Balances do
...> "source" => "foo"
...> }
...> ]
...>
...>
...> Numscriptex.Balances.put(account_assets, postings)
[
%{
Expand Down Expand Up @@ -144,7 +144,7 @@ defmodule Numscriptex.Balances do
end
end

def maybe_drop_balance(balances) do
defp maybe_drop_balance(balances) do
Enum.reject(balances, fn balance ->
balance["initial_balance"] == 0 and
balance["final_balance"] == 0
Expand Down
17 changes: 14 additions & 3 deletions lib/numscriptex/check_log.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,24 @@ defmodule Numscriptex.CheckLog do
line: nil,
message: nil

@typedoc """
Type that represents `Numscriptex.CheckLog` struct.

## Fields
* `:character` the character position where the log occurred
* `:level` the log level
* `:line` the line where the log occur
* `:message` the log message
"""
@type t() :: %__MODULE__{
character: integer(),
level: atom(),
line: integer(),
character: pos_integer(),
level: log_levels(),
line: pos_integer(),
message: binary()
}

@type log_levels() :: :error | :warning | :hint | :info

@doc """
Get a map with log data about a checked numscript.

Expand Down
24 changes: 11 additions & 13 deletions lib/numscriptex/run.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
defmodule Numscriptex.Run do
@moduledoc """
A [numscript](https://docs.formance.com/numscript/) needs some other data aside from
the script itself to run correctly, and `Numscriptex.Run` solves this problem.

If you want to know what exactly these additional fields are, you can learn more on
[numscript playground](https://playground.numscript.org/?template=simple-send)
and the [numscript docs](https://docs.formance.com/numscript/).
A Numscript needs some other data aside from the script itself to run correctly,
and `Numscriptex.Run` solves this problem. If you want to know what exactly these
additional fields are, you can learn more on [Numscript Playground](https://playground.numscript.org/?template=simple-send) and the [Numscript Docs](https://docs.formance.com/numscript/).
"""

@derive JSON.Encoder
Expand All @@ -25,9 +22,10 @@ defmodule Numscriptex.Run do
@typedoc """
Type that represents `Numscriptex.Run` struct.

`:balances`: the account's assets balances.
`:metadata`: metadata variables.
`:variables`: variables used inside the script.
## Fields
* `:balances` a map with account's assets balances
* `:metadata` [metada variables](https://docs.formance.com/numscript/reference/metadata)
* `:variables` [variables](https://docs.formance.com/numscript/reference/variables) used inside the script
"""
@type t() :: %__MODULE__{
variables: map(),
Expand All @@ -51,15 +49,15 @@ defmodule Numscriptex.Run do
def new, do: %__MODULE__{}

@doc """
Puts the chosen value under the field key on the `Numscriptex.Run` struct as
Puts the chosen value under the field key on the `Numscriptex.Run` struct as
long both are valid.

```elixir
iex> struct = Numscriptex.Run.new()
...> balances = %{"foo" => %{"USD/2" => 500, "EUR/2" => 300}}
...>
...> Numscriptex.Run.put(struct, :balances, balances)
{:ok,
{:ok,
%Numscriptex.Run{
balances: %{"foo" => %{"USD/2" => 500, "EUR/2" => 300}},
variables: %{},
Expand All @@ -84,7 +82,7 @@ defmodule Numscriptex.Run do
do: {:error, :invalid_field, %{details: "The field '#{field}' does not exists."}}

def put(_run_struct, _field, value) when not is_map(value),
do: {:error, :invalid_value, %{details: "Values argument must be a map."}}
do: {:error, :invalid_value, %{details: "Argument `value` must be a map."}}

def put(%__MODULE__{} = run_struct, :balances, value) do
{:ok, Map.replace(run_struct, :balances, Utilities.normalize_keys(value, :string))}
Expand All @@ -102,7 +100,7 @@ defmodule Numscriptex.Run do
...> balances = [%{"foo" => %{"USD/2" => 500, "EUR/2" => 300}}]
...>
...> Numscriptex.Run.put!(struct, :balances, balances)
** (ArgumentError) Values argument must be a map.
** (ArgumentError) Argument `value` must be a map.
```
"""
@spec put!(t(), atom(), map()) :: t() | no_return()
Expand Down
29 changes: 27 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
defmodule Numscriptex.MixProject do
use Mix.Project

@source_url "https://github.com/PagoPlus/numscriptex"
@version "0.1.0"

def project do
[
app: :numscriptex,
version: "0.1.0",
elixir: "~> 1.17",
version: @version,
elixir: "~> 1.18",
start_permanent: Mix.env() == :prod,
description: "Numscript support for Elixir",
aliases: aliases(),
deps: deps(),
package: package(),
docs: docs(),
test_coverage: [tool: ExCoveralls],
preferred_cli_env: [
"coveralls.html": :test,
Expand All @@ -30,6 +36,7 @@ defmodule Numscriptex.MixProject do
[
{:wasmex, "~> 0.9.2"},
{:excoveralls, "~> 0.18", only: :test},
{:ex_doc, "~> 0.34", only: :dev, runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}
]
Expand All @@ -44,4 +51,22 @@ defmodule Numscriptex.MixProject do
]
]
end

defp docs do
[
main: "readme",
name: "NumscriptEx",
suorce_ref: "v#{@version}",
source_url: @source_url,
extras: ["README.md"]
]
end

defp package do
%{
licenses: ["MIT"],
links: %{"GitHub" => @source_url},
maintaners: ["Vinicius Costa", "Fernando Mumbach"]
}
end
end
6 changes: 6 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@
"castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"},
"credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"},
"dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
"earmark_parser": {:hex, :earmark_parser, "1.4.42", "f23d856f41919f17cd06a493923a722d87a2d684f143a1e663c04a2b93100682", [:mix], [], "hexpm", "6915b6ca369b5f7346636a2f41c6a6d78b5af419d61a611079189233358b8b8b"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"ex_doc": {:hex, :ex_doc, "0.36.1", "4197d034f93e0b89ec79fac56e226107824adcce8d2dd0a26f5ed3a95efc36b1", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d7d26a7cf965dacadcd48f9fa7b5953d7d0cfa3b44fa7a65514427da44eafd89"},
"excoveralls": {:hex, :excoveralls, "0.18.3", "bca47a24d69a3179951f51f1db6d3ed63bca9017f476fe520eb78602d45f7756", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "746f404fcd09d5029f1b211739afb8fb8575d775b21f6a3908e7ce3e640724c6"},
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
"hpax": {:hex, :hpax, "1.0.1", "c857057f89e8bd71d97d9042e009df2a42705d6d690d54eca84c8b29af0787b0", [:mix], [], "hexpm", "4e2d5a4f76ae1e3048f35ae7adb1641c36265510a2d4638157fbcb53dda38445"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"},
"rustler": {:hex, :rustler, "0.35.1", "ec81961ef9ee833d721dafb4449cab29b16b969a3063a842bb9e3ea912f6b938", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "3713b2e70e68ec2bfa8291dfd9cb811fe64a770f254cd9c331f8b34fa7989115"},
Expand Down
Loading