diff --git a/.github/workflows/rainix.yaml b/.github/workflows/rainix.yaml
new file mode 100644
index 00000000..ec3ad594
--- /dev/null
+++ b/.github/workflows/rainix.yaml
@@ -0,0 +1,25 @@
+name: Rainix CI
+on: [push]
+
+concurrency:
+ group: ${{ github.ref }}-rainix
+ cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
+
+jobs:
+ standard-tests:
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ task: [rainframe-test]
+ fail-fast: false
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+ - name: Install Nix
+ uses: DeterminateSystems/nix-installer-action@v4
+ - uses: DeterminateSystems/magic-nix-cache-action@v2
+ - name: Run ${{ matrix.task }}
+ run: nix develop -c ${{ matrix.task }}
\ No newline at end of file
diff --git a/flake.nix b/flake.nix
index 071876d1..32624bdd 100644
--- a/flake.nix
+++ b/flake.nix
@@ -8,9 +8,29 @@
outputs = { self, flake-utils, rainix }:
flake-utils.lib.eachDefaultSystem (system:
- {
- packages = rainix.packages.${system};
- devShells = rainix.devShells.${system};
+ let
+ pkgs = rainix.pkgs.${system};
+ in rec {
+ packages = rec {
+ rainframe-test = rainix.mkTask.${system} {
+ name = "rainframe-test";
+ body = ''
+ set -euxo pipefail
+ npm i
+ '';
+ };
+
+ } // rainix.packages.${system};
+
+ devShells.default = pkgs.mkShell {
+ packages = [
+ packages.rainframe-test
+ ];
+
+ shellHook = rainix.devShells.${system}.default.shellHook;
+ buildInputs = rainix.devShells.${system}.default.buildInputs;
+ nativeBuildInputs = rainix.devShells.${system}.default.nativeBuildInputs;
+ };
}
);
}
\ No newline at end of file
diff --git a/public/_images/arbitrum-arb-logo-full.svg b/public/_images/arbitrum-arb-logo-full.svg
new file mode 100644
index 00000000..11ba2d1c
--- /dev/null
+++ b/public/_images/arbitrum-arb-logo-full.svg
@@ -0,0 +1,48 @@
+
+
diff --git a/public/_strategies/arbitrum/0-auction-dca/auction-dca.rain b/public/_strategies/arbitrum/0-auction-dca/auction-dca.rain
new file mode 100644
index 00000000..c5b38df3
--- /dev/null
+++ b/public/_strategies/arbitrum/0-auction-dca/auction-dca.rain
@@ -0,0 +1,352 @@
+raindex-version: 8898591f3bcaa21dc91dc3b8584330fc405eadfa
+
+networks:
+ arbitrum:
+ rpc: https://rpc.ankr.com/arbitrum
+ chain-id: 42161
+ network-id: 42161
+ currency: ETH
+
+metaboards:
+ arbitrum: https://api.goldsky.com/api/public/project_clv14x04y9kzi01saerx7bxpg/subgraphs/mb-arbitrum/0.1/gn
+
+subgraphs:
+ arbitrum: https://api.goldsky.com/api/public/project_clv14x04y9kzi01saerx7bxpg/subgraphs/ob4-arbitrum/0.1/gn
+
+orderbooks:
+ arbitrum:
+ address: 0x550878091b2B1506069F61ae59e3A5484Bca9166
+ network: arbitrum
+ subgraph: arbitrum
+
+deployers:
+ arbitrum:
+ address: 0x9B0D254bd858208074De3d2DaF5af11b3D2F377F
+ network: arbitrum
+
+tokens:
+ arbitrum-wbtc:
+ network: arbitrum
+ address: 0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f
+ decimals: 8
+ arbitrum-weth:
+ network: arbitrum
+ address: 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1
+ decimals: 18
+
+orders:
+ arbitrum-wbtc-weth:
+ orderbook: arbitrum
+ inputs:
+ - token: arbitrum-weth
+ outputs:
+ - token: arbitrum-wbtc
+
+scenarios:
+ arbitrum:
+ orderbook: arbitrum
+ runs: 1
+ bindings:
+ raindex-subparser: 0xb06202aA3Fe7d85171fB7aA5f17011d17E63f382
+
+charts:
+ arbitrum:
+
+deployments:
+ arbitrum-wbtc-weth:
+ order: arbitrum-wbtc-weth
+ scenario: arbitrum
+
+gui:
+ name: Auction based cost averaging
+ description: >
+ Swap some token for another token regularly over time, using a preset budget.
+
+ This is called "cost averaging" because spreading out a single large swap
+ into many smaller swaps over time reduces the impact of temporary market
+ movements.
+
+ The strategy works by repeatedly auctioning off tokens with an exponential
+ decay in the price, that resets each trade.
+ deployments:
+ - deployment: arbitrum-wbtc-weth
+ name: Sell WBTC for WETH on Arbitrum.
+ description: >
+ Participate in [the flippening](https://ultrasound.money/#flippening) by swapping WBTC for WETH on Arbitrum.
+ deposits:
+ - token: arbitrum-wbtc
+ min: 0
+ presets:
+ - 0
+ - 0.0001
+ - 0.001
+ - 0.01
+ - 0.1
+ - 0.2
+
+ fields:
+ - binding: time-per-amount-epoch
+ name: Budget period (in seconds)
+ description: >
+ The budget is spent over this time period.
+
+
+ For example, if the budget is daily then this is 86400 seconds (24 * 60 * 60).
+ min: 1
+ presets:
+ - name: Per minute (60)
+ value: 60
+ - name: Per hour (3600)
+ value: 3600
+ - name: Per day (86400)
+ value: 86400
+ - name: Per week (604800)
+ value: 604800
+ - name: Per 30 days (2592000)
+ value: 2592000
+ - name: Per 365 days (31536000)
+ value: 31536000
+ - binding: amount-per-epoch
+ name: Budget (WBTC per period)
+ description: >
+ The amount of WBTC to spend each budget period.
+
+ For example, if the budget is daily and this is 0.01 then 0.01 WBTC will be sold for WETH each day.
+ min: 0
+ - binding: max-trade-amount
+ name: Maximum trade size
+ description: >
+ The maximum amount of WBTC to sell in a single auction.
+ min: 0
+ - binding: min-trade-amount
+ name: Minimum trade size
+ description: >
+ The minimum amount of WBTC to sell in a single auction.
+ min: 0
+ - binding: time-per-trade-epoch
+ name: Auction period (in seconds)
+ description: >
+ The auction period is the time between each auction price halvening.
+
+ As the auction is an exponential decay, the price will halve every time this period passes.
+
+ For example, if the auction period is 1 hour then this is 3600 seconds (60 * 60).
+ If this hourly auction starts at 30 WETH per WBTC, and the baseline is 10 WETH per WBTC,
+ then the price will be 20 WETH per WBTC after 1 hour (halfway between 30 and 10),
+ 15 WETH per WBTC after 2 hours (halfway between 20 and 10),
+ and so on.
+ presets:
+ - name: Every 20 minutes (1200)
+ value: 1200
+ - name: Every 30 minutes (1800)
+ value: 1800
+ - name: Every hour (3600)
+ value: 3600
+ - name: Every 2 hours (7200)
+ value: 7200
+ - name: Every 3 hours (10800)
+ value: 10800
+ - name: Every 6 hours (21600)
+ value: 21600
+ - name: Every 12 hours (43200)
+ value: 43200
+ - name: Every 24 hours (86400)
+ value: 86400
+ - binding: baseline
+ name: Baseline price
+ description: >
+ The absolute minimum amount of WETH per WBTC that the auction will
+ trade at.
+
+ This is the inverse (i.e. `1 / x`) of the ETH/BTC ratio.
+
+ For example, 20 WETH per WBTC would mean never selling WBTC when the
+ ETH/BTC ratio is worse than 0.05.
+
+ This can be set to 0 to disable the baseline.
+ presets:
+ - name: 10
+ value: 10
+ - name: 20
+ value: 20
+ - name: 25
+ value: 25
+ - name: 30
+ value: 30
+ - binding: next-trade-multiplier
+ name: Auction start multiplier
+ description: >
+ The multiplier to apply to the last trade to kick off the next auction.
+
+ For example, if this is 1.1 and the last trade was at 20 WETH per WBTC,
+ then the next auction will start at 22 WETH per WBTC.
+ min: 1.01
+ presets:
+ - name: 1.01x
+ value: 1.01
+ - name: 1.02x
+ value: 1.02
+ - name: 1.05x
+ value: 1.05
+ - name: 1.1x
+ value: 1.1
+ - binding: next-trade-baseline-multiplier
+ name: Auction end multiplier
+ description: >
+ The multiplier to apply to the last trade to set the baseline for the next auction.
+
+ For example, if this is 0.9 and the last trade was at 20 WETH per WBTC,
+ then the next auction will end at 18 WETH per WBTC.
+
+ Note that this moving baseline is ignored if it goes below the baseline.
+ I.e. the absolute minimum baseline overrides this if necessary.
+
+ This can be set to 0 to disable the moving baseline, and should be less than 1.
+ presets:
+ - name: Disabled (0)
+ value: 0
+ - name: 0.7x
+ value: 0.7
+ - name: 0.8x
+ value: 0.8
+ - name: 0.9x
+ value: 0.9
+ - name: 0.95x
+ value: 0.95
+ - name: 0.99x
+ value: 0.99
+ - binding: initial-io
+ name: Kickoff WETH per WBTC
+ description: >
+ The initial WETH per WBTC to kickoff the first auction.
+
+ The strategy will pretend that a trade was made at this price before
+ the first auction, so that everything else can be calculated from there.
+
+ This is WETH per WBTC, so if the ETH/BTC ratio is 0.05 then this should be 20.
+
+ You can set this much higher than the market rates to start the strategy
+ off easily, for example you could set it at 50 if the market was around 20-25.
+
+ There is no need for this to be exact, it just has to be ballpark and
+ definitely higher than the market. Any number within 1-10x the real
+ price should be fine.
+
+ This should be greater than the baseline.
+ min: 0
+ presets:
+ - name: 50
+ value: 50
+ - name: 100
+ value: 100
+
+---
+#raindex-subparser !Raindex subparser.
+
+#time-per-amount-epoch !Duration of one unit of streaming amount halflife.
+#amount-per-epoch !Amount of output token to approve for buying per epoch.
+#min-trade-amount !Each trade must be at least this many output tokens.
+#max-trade-amount !Each trade will be capped at this many tokens.
+
+#time-per-trade-epoch !Duration of one unit of io ratio halflife.
+#baseline !Minimum io ratio. This component of the io ratio is ignored by the halflife calculations.
+
+#next-trade-multiplier !Start next auction at this x the last trade.
+#next-trade-baseline-multiplier !Lifts the baseline to here relative to the previous trade.
+#initial-io !Strat will be initialized with this as the starting last trade. Must be larger than baseline.
+
+#last-trade-time-key "last-trade-time"
+#last-trade-io-key "last-trade-io"
+#initial-time-key "initial-time"
+#amount-used-key "amount-used"
+
+#set-last-trade
+last-io:,
+:set(hash(order-hash() last-trade-time-key) now()),
+:set(hash(order-hash() last-trade-io-key) last-io);
+
+#set-initial-time
+:set(hash(order-hash() initial-time-key) now());
+
+#get-initial-time
+:get(hash(order-hash() initial-time-key));
+
+#get-last-trade
+last-time:get(hash(order-hash() last-trade-time-key)),
+last-io:get(hash(order-hash() last-trade-io-key));
+
+#get-epoch
+last-time _: call<'get-last-trade>(),
+duration: sub(now() last-time),
+initial-time: call<'get-initial-time>(),
+total-duration: sub(now() initial-time),
+amount-epochs: div(total-duration time-per-amount-epoch),
+trade-epochs: div(duration time-per-trade-epoch);
+
+#amount-for-epoch
+amount-epochs:,
+total-available: linear-growth(0 amount-per-epoch amount-epochs),
+used: get(hash(order-hash() amount-used-key)),
+unused: sub(total-available used),
+capped-unused: min(unused max-trade-amount);
+
+#halflife
+max-val epoch:,
+/**
+ * Shrinking the multiplier like this
+ * then applying it 10 times allows for
+ * better precision when max-io-ratio
+ * is very large, e.g. ~1e10 or ~1e20+
+ *
+ * This works because `power` loses
+ * precision on base `0.5` when the
+ * exponent is large and can even go
+ * to `0` while the io-ratio is still
+ * large. Better to keep the multiplier
+ * higher precision and drop the io-ratio
+ * smoothly for as long as we can.
+ */
+multiplier:
+ power(0.5 div(epoch 10)),
+val:
+ mul(
+ max-val
+ multiplier
+ multiplier
+ multiplier
+ multiplier
+ multiplier
+ multiplier
+ multiplier
+ multiplier
+ multiplier
+ multiplier
+ );
+
+#io-for-epoch
+epoch:,
+last-io: call<'get-last-trade>(),
+max-next-trade: mul(last-io next-trade-multiplier),
+baseline-next-trade: mul(last-io next-trade-baseline-multiplier),
+real-baseline: max(baseline-next-trade baseline),
+variable-component: saturating-sub(max-next-trade real-baseline),
+above-baseline: call<'halflife>(variable-component epoch),
+_: add(real-baseline above-baseline);
+
+#handle-add-order
+using-words-from raindex-subparser
+:call<'set-last-trade>(initial-io),
+:call<'set-initial-time>();
+
+#calculate-io
+using-words-from raindex-subparser
+amount-epochs
+trade-epochs:call<'get-epoch>(),
+max-output: call<'amount-for-epoch>(amount-epochs),
+io: call<'io-for-epoch>(trade-epochs),
+:call<'set-last-trade>(io);
+
+#handle-io
+:ensure(greater-than-or-equal-to(output-vault-decrease() min-trade-amount) "Min trade amount."),
+used: get(hash(order-hash() amount-used-key)),
+:set(hash(order-hash() amount-used-key) add(used output-vault-decrease()));
\ No newline at end of file
diff --git a/public/_strategies/arbitrum/0-auction-dca/description.md b/public/_strategies/arbitrum/0-auction-dca/description.md
new file mode 100644
index 00000000..e69de29b
diff --git a/public/_strategies/arbitrum/frame.md b/public/_strategies/arbitrum/frame.md
new file mode 100644
index 00000000..51cbf930
--- /dev/null
+++ b/public/_strategies/arbitrum/frame.md
@@ -0,0 +1,3 @@
+# Test project
+
+Test description
\ No newline at end of file
diff --git a/public/_strategies/arbitrum/webapp.md b/public/_strategies/arbitrum/webapp.md
new file mode 100644
index 00000000..66f29d05
--- /dev/null
+++ b/public/_strategies/arbitrum/webapp.md
@@ -0,0 +1,15 @@
+
+
+# Raindex strategies on Arbitrum
+
+## Want to run a Raindex strategy on Arbitrum?
+
+Raindex allows anyone to deploy non-custodial, perpetual, and automated trading strategies on Arbitrum.
+
+## How to deploy
+
+1. Choose a strategy - this page has a number of strategies that you can choose from. Choose the strategy that aligns with your convictions.
+
+2. Watch the videos and follow the instructions to configure and deploy the strategy.
+
+3. Monitor the performance of the strategy, deposit and withdraw funds at any time.
\ No newline at end of file