Intents
Understanding the Intent System
Intents are the cornerstone of Saline. An intent is a predicate or a combination of predicates that specifies what actions an account allows to happen. Intents enable powerful patterns like account abstraction, delegation, token swaps, multi-signature authorization and more.
Key Characteristics of Intents:
Declarative: Intents declare what is permitted rather than how to do it
Composable: Complex authorization rules are built by combining simpler intents
Enforceable: The blockchain enforces intents at transaction execution time
Future-proof: Intents work with transactions that may not exist yet (like future swap matches)
Intent Primitives and Composites
Saline provides several basic building blocks that can be combined to create complex intents:
Primitives
Restriction(): Require two expressions be related in a given waySignature(): Require signature from a given public key
Composites
All(conditions: list): Requires that all sub-intents/conditions in the list are fulfilled (logical AND).Any(threshold: int, conditions: list): Requires that at least `threshold` sub-intents/conditions in the list are fulfilled (logical OR or M-of-N).
Modifiers
Temporary(expiry_timestamp: int, available_after: bool, intent): Restricts an intent to be valid only up to a specific Unix timestamp.Finite(max_uses: int, intent): Restricts an intent to a maximum number of uses.
Expressions
Expressions are used within Restriction intents to evaluate conditions based on transaction details or account state:
Lit(value): Represents a literal value (e.g., number, string).Balance(token: str):Balance of the specified token for the account hosting the intent.Receive(token: Token): Represents the amount of a token received.Send(token: Token): Represents the amount of a token sent.Arithmetic2(op: ArithOp, lhs, rhs): Elementary arithmetic operations (Add, Sub, Mul, Div) over expressions.
Operator Syntax (Optional Shorthands)
For convenience, the SDK may overload some Python operators as shorthands for common intent constructions. However, using the explicit binding classes (Restriction, All, Any, Arithmetic2, etc.) is generally recommended for clarity, especially for complex intents. The examples below primarily use the explicit bindings.
Potential Shorthands (Check SDK source or specific examples for current support):
- &: Shorthand for All([...])
- |: Shorthand for Any(1, [...]) (logical OR)
- <=, >=, <, >: Create a Restriction between two expressions.
- *, +, -, /: Create an Arithmetic2 expression.
Counterparty: The account on the other side of the transaction
None: Any account
"public_key": A specific account
Token: The token type for the flow
Token.BTC,Token.ETH, etc.
Common Intent Patterns
Swap Intent Pattern
# Define a concrete swap intent: I want to swap 2 ETH for 100 USDT
intent = Send(Token.ETH) <= 2 & Receive(Token.USDT) >= 100
# Define a rate swap intent: I want 100 USDT for each 2 ETH
intent = Send(Token.ETH) * 2 <= Receive(Token.USDT) * 100
Breaking Down the Pattern:
Send(Token.ETH): the amount of sent ETH* 2: multiplies by 2<=: Sets up the exchange relationship (less than or equal)Receive(Token.USDT): the amount of received USDT* 100: multiplies by 100
Multi-Signature Intent Pattern
This intent requires at least 2 signatures from the 3 defined signers to authorize any transaction.
# Define the signers
sig1 = Signature("public_key_1")
sig2 = Signature("public_key_2")
sig3 = Signature("public_key_3")
# Create a 2-of-3 multisig intent
multisig_intent = Any(2, [sig1, sig2, sig3])
Complete Swap Intent Example
from saline_sdk.account import Account
from saline_sdk.transaction.bindings import (
NonEmpty, Transaction, SetIntent, Token,
Send, Receive
)
from saline_sdk.transaction.tx import prepareSimpleTx
from saline_sdk.rpc.client import Client
# Create account
account = Account.from_mnemonic("your mnemonic here").create_subaccount(label="swap_account")
# Define swap parameters
give_token = Token.ETH
give_amount = 2
take_token = Token.USDT
take_amount = 100
# Create swap intent using operator syntax
intent = Send(give_token) * give_amount <= Receive(take_token) * take_amount
# Create a SetIntent instruction and transaction
set_intent = SetIntent(account.public_key, intent)
tx = Transaction(instructions=NonEmpty.from_list([set_intent]))
signed_tx = prepareSimpleTx(account, tx)
# Submit to blockchain
client = Client()
result = await client.tx_commit(signed_tx)
Advanced Intent Patterns (Coming Soon)
Time-Limited Intent
Creating an intent that expires after a specific time:
# Base intent (e.g., token swap)
base_intent = Send(Token.ETH) * 1 <= Receive(Token.USDT) * 50
# Set expiry time (Unix timestamp) - e.g., 1 day from now
import time
availableAfter = true
expiry_time = int(time.time()) + (24 * 60 * 60)
# Create a time-limited intent
limited_intent = Temporary(expiry_time, availableAfter, base_intent)
Usage-Limited Intent
Creating an intent that can only be used a specific number of times:
# Base intent
base_intent = Send(Token.ETH) * 0.1 <= Receive(Token.USDT) * 5
# Create an intent limited to 5 uses
limited_intent = Finite(5, base_intent)
Best Practices
Use Explicit Bindings: Prefer Restriction, All, Any for clarity over operator shorthands, especially for non-trivial intents.
Start Simple: Begin with basic patterns (like fixed swaps) and gradually add complexity (Any, Temporary, Finite).
Test Extensively: Verify intents behave as expected with various transaction patterns.
Consider Modifiers: For sensitive operations, consider adding Temporary or Finite constraints.