Smart Handles is an abstract Cardano contract generator for instantiating validators that are meant to be carried out by incentivised agents to handle the requests in a decentralized and trustless manner.
One of the simplest use-cases can be an instance for swapping one specific token
to another without going through a DEX UI directly. This instance will have its
unique address, which can be complemented with AdaHandles (e.g. @ada-to-min
)
and therefore any wallet that supports Smart Handles configurations can send
some $ADA directly to this address to be swapped for $MIN.
This is not limited to swaps of course. For instance, sending funds to
@offer-spacebudz
(which resolves to a router smart contract for SpaceBudz
collection offers) could create a collection offer for SpaceBudz.
The example here uses Smart Handles for swapping any token pairs through Minswap exchange.
This project is funded by the Cardano Treasury in Catalyst Fund 10.
Right now, most interaction with smart contract protocols is done through centralized front-end services where the transactions are built and submitted through centralized backend infrastructure. In addition to the negative impact this has on decentralization, it also hampers adoption due to the restrictions it imposes. For instance, users with mobile wallets have severely limited options when it comes to interacting with DApps. Also, regular users will be unable to interact with most DApps if the DApp front-ends were to become unavailable for any reason or if the backend was down.
There are a few attempts to address this problem, such as DApp Schemas or Smart Contract Blueprints; however, all of these solutions rely on off-chain infrastructure to specify how to interpret a DApp's datums, redeemers, and other on-chain data in order to build transactions. Smart Beacons differ from these other approaches in that it is a fully onchain solution that does not rely on offchain infrastructure.
UTxOs locked at a Smart Handles instance can carry datums that determine how a routing agent should reproduce them at their specified destination addresses (or route addresses).
In case of simple datums however, the interaction logic is hard-coded in the instance itself, and their corresponding off-chain agents should provide routers with all they need to handle requests.
Smart Handles framework offers two datums:
data PSmartHandleDatum (s :: S)
= PSimple (Term s (PDataRecord '["owner" ':= PAddress]))
| PAdvanced
( Term
s
( PDataRecord
'[ "mOwner" ':= PMaybeData PAddress
, "routerFee" ':= PInteger
, "reclaimRouterFee" ':= PInteger
, "routeRequiredMint" ':= PRequiredMint
, "reclaimRequiredMint" ':= PRequiredMint
, "extraInfo" ':= PData
]
)
)
Simple
, which only carries an owner addressAdvanced
, which allows for a more involved routing/reclaim scenarios:mOwner
is an optional owner (if this is set toNothing
it makes the UTxO un-reclaimable)routerFee
specifies how much an agent must be compensated for routing a requestreclaimRouterFee
is similar torouterFee
for invoking the advanced reclaim endpoint (this helps with balancing of the incentive structure)routeRequiredMint
is a value (isomorphic withMaybe (PolicyID, TokenName)
) that specifies whether a route output must append the same quantity of mints/burns present in the transactionreclaimRequiredMint
is similar torouteRequiredMint
, but for the advanced reclaim endpointextraInfo
is an arbitraryPData
provided for instances
On top of that, each instance can also support a "single" variant and a "batch" variant: Single will be a spending script that only supports a single route/reclaim per transaction. Batch, on the other hand, is a staking script for handling multiple requests in single transactions.
Single variants will have 3 redeemers: routing, simple reclaims, and advanced reclaims:
data PSmartHandleRedeemer (s :: S)
= PRoute (Term s (PDataRecord '["ownIndex" ':= PInteger, "routerIndex" ':= PInteger]))
| PReclaim (Term s (PDataRecord '[]))
| PAdvancedReclaim (Term s (PDataRecord '["ownIndex" ':= PInteger, "routerIndex" ':= PInteger]))
Simple reclaim only applies to simple datums, and the only requirement is imposes on withdrawals is the signature of the owner. Advanced reclaim passes the spending UTxO to instance's underlying validator, and therefore has a redeemer similar to the routing endpoint.
Batch variants' scripts (spending and staking) will have 2 redeemers each:
-- for the staking script
data PRouterRedeemer (s :: S)
= PRouterRedeemer
( Term
s
( PDataRecord
'[ "inputIdxs" ':= PBuiltinList (PAsData PInteger)
, "outputIdxs" ':= PBuiltinList (PAsData PInteger)
]
)
)
-- for the batch spend script
data PSmartRedeemer (s :: S)
= PRouteSmart (Term s (PDataRecord '[]))
| PReclaimSmart (Term s (PDataRecord '[]))
The underlying logic of an instance is shared between the two variants, and therefore utilizing either one will be very similar.
You may have noticed that both redeemers are using the UTxO indexer pattern for a more optimized performance.
There are 2 packages available for working with smart handles instances:
smart-handles-offchain
offers config interfaces to build transactions for all possible endpointssmart-handles-agent
provides a CLI application generator function that allows instances to have curated user-friendly Node applications for submitting, routing and reclaiming requests
Before you begin, ensure you have Nix installed on your system. Nix is used for package management and to provide a consistent development environment. If you don't have Nix installed, you can do so by running the following command:
sh <(curl -L https://nixos.org/nix/install) --daemon
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
Make sure to enable Nix Flakes by editing either ~/.config/nix/nix.conf
or /etc/nix/nix.conf
on
your machine and add the following configuration entries:
experimental-features = nix-command flakes ca-derivations
allow-import-from-derivation = true
Optionally, to improve build speed, it is possible to set up binary caches by adding additional configuration entries:
substituters = https://cache.nixos.org https://cache.iog.io https://cache.zw3rk.com
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= loony-tools:pr9m4BkM/5/eSTZlkQyRt57Jz7OMBxNSUiMC4FkcNfk=
To facilitate seamlessly moving between directories and associated Nix development shells we use direnv and nix-direnv:
Your shell and editors should pick up on the .envrc
files in different
directories and prepare the environment accordingly. Use direnv allow
to
enable the direnv environment and direnv reload
to reload it when necessary.
Otherwise, the .envrc
file contains a proper Nix target which will be used
with the nix develop --accept-flake-config
command.
To install both using nixpkgs
:
nix profile install nixpkgs#direnv
nix profile install nixpkgs#nix-direnv
Once Nix is installed, you should be able to seamlessly use the repository to develop, build and run packages.
Download the Git repository:
git clone https://github.com/Anastasia-Labs/smart-handles.git
Navigate to the repository directory:
cd smart-handles
Activate the development environment with Nix:
nix develop --accept-flake-config
Or
make shell
Please be patient when building nix development environment for the first time,
as it may take a very long time. Subsequent builds should be faster.
Additionally, when you run nix run .#help
you'll get a list of scripts you can
run, the Github CI (nix flake check) is setup in a way where it checks the
project builds successfully, haskell format is done correctly, and commit
message follows conventional commits. Before pushing you should
run cabal run
, nix run .#haskellFormat
(automatically formats all haskell
files, including cabal), if you want to commit a correct format message you can
run cz commit
.
Build:
make build
Execute the test suite:
make test
Compile and export Plutarch scripts:
make export
Head over to the off-chain SDK of smart handles to learn how to define your instance's off-chain, or look through an example with Minswap V1.
© 2024 Anastasia Labs.
All code is licensed under MIT License. See LICENSE file for details.