Skip to content

Commit da10903

Browse files
committed
cmd/loop: add CLI support for scriptable autoloop
Add new flags to 'loop setparams' for configuring scriptable autoloop: - --scriptautoloop: Enable/disable scriptable autoloop mode - --scriptfile: Path to a Starlark script file (recommended for production - enables version control, syntax highlighting, and proper editing workflows) - --script: Inline Starlark script for simple cases - --scripttickinterval: Custom tick interval in seconds Example usage: # Enable with script file (recommended) loop setparams --scriptautoloop --scriptfile /path/to/autoloop.star # Enable with inline script loop setparams --scriptautoloop --script 'decisions = []' # Disable loop setparams --scriptautoloop=false The --scriptfile and --script flags are mutually exclusive. Scriptable autoloop cannot be used together with easy autoloop.
1 parent e4edd6e commit da10903

5 files changed

Lines changed: 92 additions & 16 deletions

File tree

cmd/loop/liquidity.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"os"
89
"strconv"
910
"strings"
1011

@@ -390,6 +391,34 @@ var setParamsCommand = &cli.Command{
390391
"save on chain fees. Not setting this flag " +
391392
"therefore might result in a lower swap fees",
392393
},
394+
&cli.BoolFlag{
395+
Name: "scriptautoloop",
396+
Usage: "set to true to enable scriptable autoloop " +
397+
"using Starlark scripts. This allows custom " +
398+
"swap logic with variables, functions, loops, " +
399+
"and sorting. Mutually exclusive with " +
400+
"easyautoloop and threshold rules.",
401+
},
402+
&cli.StringFlag{
403+
Name: "scriptfile",
404+
Usage: "path to a Starlark script file for " +
405+
"scriptable autoloop. The script must set " +
406+
"a 'decisions' variable to a list of swaps. " +
407+
"Recommended for production use as it allows " +
408+
"version control and proper editing.",
409+
},
410+
&cli.StringFlag{
411+
Name: "script",
412+
Usage: "inline Starlark script for scriptable " +
413+
"autoloop. For simple scripts only; use " +
414+
"--scriptfile for complex scripts.",
415+
},
416+
&cli.Uint64Flag{
417+
Name: "scripttickinterval",
418+
Usage: "custom tick interval in seconds for " +
419+
"scriptable autoloop. If not set, uses " +
420+
"the default 20-minute interval.",
421+
},
393422
},
394423
Action: setParams,
395424
}
@@ -637,6 +666,48 @@ func setParams(ctx context.Context, cmd *cli.Command) error {
637666
params.FastSwapPublication = true
638667
}
639668

669+
// Handle scriptable autoloop flags.
670+
if cmd.IsSet("scriptautoloop") {
671+
params.ScriptableAutoloop = cmd.Bool("scriptautoloop")
672+
flagSet = true
673+
}
674+
675+
// Handle script content from file or inline.
676+
scriptFileSet := cmd.IsSet("scriptfile")
677+
scriptInlineSet := cmd.IsSet("script")
678+
679+
if scriptFileSet && scriptInlineSet {
680+
return fmt.Errorf("cannot set both --scriptfile and --script; " +
681+
"use one or the other")
682+
}
683+
684+
if scriptFileSet {
685+
scriptPath := cmd.String("scriptfile")
686+
scriptBytes, err := os.ReadFile(scriptPath)
687+
if err != nil {
688+
return fmt.Errorf("failed to read script file %s: %w",
689+
scriptPath, err)
690+
}
691+
params.ScriptableScript = string(scriptBytes)
692+
flagSet = true
693+
}
694+
695+
if scriptInlineSet {
696+
params.ScriptableScript = cmd.String("script")
697+
flagSet = true
698+
}
699+
700+
if cmd.IsSet("scripttickinterval") {
701+
params.ScriptableTickIntervalSec = cmd.Uint64("scripttickinterval")
702+
flagSet = true
703+
}
704+
705+
// Validate that scriptable autoloop is not used with easyautoloop.
706+
if params.ScriptableAutoloop && params.EasyAutoloop {
707+
return fmt.Errorf("scriptable autoloop cannot be used with " +
708+
"easy autoloop; disable one before enabling the other")
709+
}
710+
640711
if !flagSet {
641712
return fmt.Errorf("at least one flag required to set params")
642713
}

liquidity/liquidity.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,19 +295,22 @@ func (m *Manager) Run(ctx context.Context) error {
295295
case <-m.cfg.AutoloopTicker.Ticks():
296296
// Check which autoloop mode to use.
297297
// Priority: scriptable > easy > threshold rules.
298-
if m.params.ScriptableAutoloop {
298+
switch {
299+
case m.params.ScriptableAutoloop:
299300
err := m.scriptableAutoLoop(ctx)
300301
if err != nil {
301302
log.Errorf("scriptable autoloop "+
302303
"failed: %v", err)
303304
}
304-
} else if m.params.EasyAutoloop {
305+
306+
case m.params.EasyAutoloop:
305307
err := m.easyAutoLoop(ctx)
306308
if err != nil {
307309
log.Errorf("easy autoloop failed: %v",
308310
err)
309311
}
310-
} else {
312+
313+
default:
311314
err := m.autoloop(ctx)
312315
switch err {
313316
case ErrNoRules:

liquidity/parameters.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ type Parameters struct {
129129
// If set to false, the deadline is set to 30 minutes.
130130
FastSwapPublication bool
131131

132-
// ScriptableAutoloop enables CEL-based scriptable autoloop mode.
132+
// ScriptableAutoloop enables Starlark-based scriptable autoloop mode.
133133
// This mode is mutually exclusive with EasyAutoloop and threshold rules.
134134
ScriptableAutoloop bool
135135

136-
// ScriptableScript is the CEL expression to evaluate on each tick.
136+
// ScriptableScript is the Starlark script to evaluate on each tick.
137137
// Required when ScriptableAutoloop is true.
138138
ScriptableScript string
139139

@@ -653,13 +653,13 @@ func ParametersToRpc(cfg Parameters) (*clientrpc.LiquidityParameters,
653653
HtlcConfTarget: cfg.HtlcConfTarget,
654654
EasyAutoloop: cfg.EasyAutoloop,
655655
EasyAutoloopLocalTargetSat: uint64(cfg.EasyAutoloopTarget),
656-
Account: cfg.Account,
657-
AccountAddrType: addrType,
658-
EasyAssetParams: easyAssetMap,
659-
FastSwapPublication: cfg.FastSwapPublication,
660-
ScriptableAutoloop: cfg.ScriptableAutoloop,
661-
ScriptableScript: cfg.ScriptableScript,
662-
ScriptableTickIntervalSec: uint64(cfg.ScriptableTickInterval / time.Second),
656+
Account: cfg.Account,
657+
AccountAddrType: addrType,
658+
EasyAssetParams: easyAssetMap,
659+
FastSwapPublication: cfg.FastSwapPublication,
660+
ScriptableAutoloop: cfg.ScriptableAutoloop,
661+
ScriptableScript: cfg.ScriptableScript,
662+
ScriptableTickIntervalSec: uint64(cfg.ScriptableTickInterval / time.Second),
663663
}
664664
// Set excluded peers for easy autoloop.
665665
rpcCfg.EasyAutoloopExcludedPeers = make(

liquidity/script/builtins.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
)
88

99
// loopOutBuiltin creates a loop out swap decision.
10-
// Usage: loop_out(amount, channel_ids) or loop_out(amount, channel_ids, priority)
10+
// Usage: loop_out(amount, channel_ids) or loop_out(amount, channel_ids, priority).
1111
func loopOutBuiltin(thread *starlark.Thread, fn *starlark.Builtin,
1212
args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
1313

@@ -65,7 +65,7 @@ func loopOutBuiltin(thread *starlark.Thread, fn *starlark.Builtin,
6565
}
6666

6767
// loopInBuiltin creates a loop in swap decision.
68-
// Usage: loop_in(amount, peer_pubkey) or loop_in(amount, peer_pubkey, priority)
68+
// Usage: loop_in(amount, peer_pubkey) or loop_in(amount, peer_pubkey, priority).
6969
func loopInBuiltin(thread *starlark.Thread, fn *starlark.Builtin,
7070
args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
7171

liquidity/script_equivalence_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
)
2121

2222
// EasyAutoloopStarlarkScript is a Starlark script that replicates easy-autoloop.
23-
// Target: 100000 sats (embedded in script)
23+
// Target: 100000 sats (embedded in script).
2424
const EasyAutoloopStarlarkScript = `
2525
# Easy autoloop equivalent script in Starlark
2626
# Target: 100000 sats
@@ -58,7 +58,7 @@ def autoloop():
5858
swap_amount = min(best.local_balance, amount)
5959
return [loop_out(swap_amount, [best.channel_id])]
6060
61-
# Execute and return decisions
61+
# Execute and store result.
6262
decisions = autoloop()
6363
`
6464

@@ -335,13 +335,15 @@ func TestScriptableAutoloopIntegration(t *testing.T) {
335335
},
336336
Restrictions: func(ctx context.Context, swapType swap.Type,
337337
initiator string) (*Restrictions, error) {
338+
338339
return &Restrictions{
339340
Minimum: 10000,
340341
Maximum: 1000000,
341342
}, nil
342343
},
343344
LoopOutQuote: func(ctx context.Context,
344345
req *loop.LoopOutQuoteRequest) (*loop.LoopOutQuote, error) {
346+
345347
return &loop.LoopOutQuote{
346348
SwapFee: 100,
347349
PrepayAmount: 1000,

0 commit comments

Comments
 (0)