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 way

  • Signature(): 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.

  1. Counterparty: The account on the other side of the transaction

  • None: Any account

  • "public_key": A specific account

  1. 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:

  1. Send(Token.ETH): the amount of sent ETH

  2. * 2: multiplies by 2

  3. <=: Sets up the exchange relationship (less than or equal)

  4. Receive(Token.USDT): the amount of received USDT

  5. * 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

  1. Use Explicit Bindings: Prefer Restriction, All, Any for clarity over operator shorthands, especially for non-trivial intents.

  2. Start Simple: Begin with basic patterns (like fixed swaps) and gradually add complexity (Any, Temporary, Finite).

  3. Test Extensively: Verify intents behave as expected with various transaction patterns.

  4. Consider Modifiers: For sensitive operations, consider adding Temporary or Finite constraints.