@dev.sail.money/sailor 0.1.0-local → 1.0.0-38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +139 -140
- package/LICENSE +21 -21
- package/README.md +428 -430
- package/docs/PERMISSION_MODEL.md +93 -93
- package/examples/permissions/BoundedApproveAndCallBatch.sol +179 -179
- package/examples/permissions/BoundedBet_Limitless_Base.sol +97 -97
- package/examples/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +94 -94
- package/examples/permissions/BoundedPerp_GMXv2_Arbitrum.sol +154 -154
- package/examples/permissions/BoundedStake_Venice_Base.sol +85 -85
- package/examples/permissions/BoundedSupply_AaveV3_Arbitrum.sol +82 -82
- package/examples/permissions/BoundedSwap_UniswapV3_Base.sol +116 -116
- package/examples/permissions/BoundedSwap_UniswapV4_Unichain.sol +150 -150
- package/examples/permissions/BoundedTransfer_ERC20_Ethereum.sol +73 -73
- package/examples/permissions/BoundedVault_ERC4626_Base.sol +97 -97
- package/examples/permissions/README.md +79 -79
- package/examples/permissions/SailCalldata.sol +118 -118
- package/examples/permissions/foundry.toml +10 -10
- package/examples/permissions/interfaces/IBatchPermission.sol +38 -38
- package/examples/permissions/interfaces/IPermission.sol +18 -18
- package/package.json +44 -45
- package/packages/cli/README.md +34 -34
- package/packages/cli/dist/index.cjs +734 -705
- package/packages/cli/dist/server.cjs +627 -538
- package/packages/sdk/README.md +65 -65
- package/packages/sdk/dist/intelligence.d.ts +1 -1
- package/packages/sdk/dist/intelligence.js +1 -1
- package/packages/sdk/package.json +80 -80
- package/packages/ui/dist/assets/{add-BxpXfVWe.js → add-Dl1etsL9.js} +1 -1
- package/packages/ui/dist/assets/{all-wallets-BKTn_sWK.js → all-wallets-C0eHLOGG.js} +1 -1
- package/packages/ui/dist/assets/{app-store-CfuKbwxR.js → app-store-B-VMDEZ3.js} +1 -1
- package/packages/ui/dist/assets/{apple-BKSBbNYg.js → apple-DkDXzKns.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-D4bG6gZi.js → arrow-bottom-DtPzuS76.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-circle-BNTs1p0T.js → arrow-bottom-circle-D7odSAO8.js} +1 -1
- package/packages/ui/dist/assets/{arrow-left-2uee3vYv.js → arrow-left-zJV9tpx0.js} +1 -1
- package/packages/ui/dist/assets/{arrow-right-BktjMV6h.js → arrow-right-BOREfe7o.js} +1 -1
- package/packages/ui/dist/assets/{arrow-top-Izu28fX4.js → arrow-top-CipQc3Af.js} +1 -1
- package/packages/ui/dist/assets/{bank-USBaAyFM.js → bank-C5s7eoV5.js} +1 -1
- package/packages/ui/dist/assets/{basic-C_9KjTEH.js → basic-D2es4Vq8.js} +1 -1
- package/packages/ui/dist/assets/{browser-DAEMAKV7.js → browser-DITQWDC9.js} +1 -1
- package/packages/ui/dist/assets/{card-DT8yDkKN.js → card-C3DDkaYK.js} +1 -1
- package/packages/ui/dist/assets/{ccip-CkqfGSxX.js → ccip-UBXL3JiN.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-bold-D2gjOQo2.js → checkmark-bold-D8yW0_K_.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-CsgdEXFj.js → checkmark-ngef3MAl.js} +1 -1
- package/packages/ui/dist/assets/{chevron-bottom-tprFynYV.js → chevron-bottom-C56BipDR.js} +1 -1
- package/packages/ui/dist/assets/{chevron-left-D2Zj1gNB.js → chevron-left-BmIPtPl_.js} +1 -1
- package/packages/ui/dist/assets/{chevron-right-D1rRuAVe.js → chevron-right-BnySHQ8h.js} +1 -1
- package/packages/ui/dist/assets/{chevron-top-24dL1mbL.js → chevron-top-BDGZnNW3.js} +1 -1
- package/packages/ui/dist/assets/{chrome-store-Vy-5niYX.js → chrome-store-BYIqJZVF.js} +1 -1
- package/packages/ui/dist/assets/{clock-qBjLnVdJ.js → clock-Bl4mUHAM.js} +1 -1
- package/packages/ui/dist/assets/{close-DARDwgcu.js → close-B9rhEX6U.js} +1 -1
- package/packages/ui/dist/assets/{coinPlaceholder-BvpIbPlD.js → coinPlaceholder-1cO0FQsl.js} +1 -1
- package/packages/ui/dist/assets/{compass-BMTO0ayt.js → compass-7i-VuXu2.js} +1 -1
- package/packages/ui/dist/assets/{copy-PaXeRHza.js → copy-OqqXix2J.js} +1 -1
- package/packages/ui/dist/assets/{core-BFnStQd-.js → core-tX9kIIDJ.js} +3 -3
- package/packages/ui/dist/assets/cursor-BoyeQ9fN.js +3 -0
- package/packages/ui/dist/assets/{cursor-transparent-BEMdi-8q.js → cursor-transparent-5aoRH67u.js} +1 -1
- package/packages/ui/dist/assets/{desktop-CfuLLThw.js → desktop-BaPXK9R6.js} +1 -1
- package/packages/ui/dist/assets/{disconnect-DhwgJMiR.js → disconnect-LlK5K1CF.js} +1 -1
- package/packages/ui/dist/assets/{discord-po8qoN1s.js → discord-BdcQNWY_.js} +1 -1
- package/packages/ui/dist/assets/{etherscan-BEsz0_yx.js → etherscan-Bb-WxpO1.js} +1 -1
- package/packages/ui/dist/assets/{events-Bz33Unzu.js → events-DjdZr6no.js} +1 -1
- package/packages/ui/dist/assets/{exclamation-triangle-7CjTAGOQ.js → exclamation-triangle-COx4VtPV.js} +1 -1
- package/packages/ui/dist/assets/{extension-CmxjEWEt.js → extension-DF63DTWO.js} +1 -1
- package/packages/ui/dist/assets/{external-link-CmQ--bNS.js → external-link-DyghCkQu.js} +1 -1
- package/packages/ui/dist/assets/{facebook-CIBn9b65.js → facebook-Dcg4bZMR.js} +1 -1
- package/packages/ui/dist/assets/{fallback-DATyrQlb.js → fallback-DJIr_fH3.js} +1 -1
- package/packages/ui/dist/assets/{farcaster-OJ3Jasxg.js → farcaster-BkmV5HjO.js} +1 -1
- package/packages/ui/dist/assets/{filters-D4x09zeL.js → filters-DLHj1T_P.js} +1 -1
- package/packages/ui/dist/assets/{github-ZlIuMArp.js → github-BFDCgKrF.js} +1 -1
- package/packages/ui/dist/assets/{google-Gwg85sfv.js → google-C08SpmIy.js} +1 -1
- package/packages/ui/dist/assets/{help-circle-D1uOWYcX.js → help-circle-DU1IFmWp.js} +1 -1
- package/packages/ui/dist/assets/{id-C0-5UdYk.js → id-DtDRGf3L.js} +1 -1
- package/packages/ui/dist/assets/{image-D_DUsv8-.js → image-CgCXJEjT.js} +1 -1
- package/packages/ui/dist/assets/{index-izd7vu_r.js → index-8chM4S5Y.js} +1 -1
- package/packages/ui/dist/assets/{index-CrYzBWfD.js → index-B5sCtNuq.js} +1 -1
- package/packages/ui/dist/assets/{index-DdbJhIdl.js → index-BBfBEazf.js} +3 -3
- package/packages/ui/dist/assets/{index-BCzex_R6.js → index-BhXPwltt.js} +1 -1
- package/packages/ui/dist/assets/index-Cm05Py20.css +1 -0
- package/packages/ui/dist/assets/{index-DiojfeVM.js → index-D37bD6Yt.js} +1 -1
- package/packages/ui/dist/assets/index-DZfBh-cg.js +1775 -0
- package/packages/ui/dist/assets/{index.es-DdkHhQAj.js → index.es-BEcNQEn-.js} +4 -4
- package/packages/ui/dist/assets/{info-CiRd_kEG.js → info-ClsdYA4P.js} +1 -1
- package/packages/ui/dist/assets/{info-circle-ypxjqarK.js → info-circle-DJmn4Bsv.js} +1 -1
- package/packages/ui/dist/assets/{lightbulb-B-pxLxd8.js → lightbulb-CXSftjXS.js} +1 -1
- package/packages/ui/dist/assets/{mail-BYmicuVZ.js → mail-cdYKOl9P.js} +1 -1
- package/packages/ui/dist/assets/{metamask-sdk-Ccl6DG7Q.js → metamask-sdk-Co3aIEln.js} +1 -1
- package/packages/ui/dist/assets/{mobile-CtP5PqVT.js → mobile-DEHYlk8L.js} +1 -1
- package/packages/ui/dist/assets/{more-6C2733we.js → more-Cq_fo8pI.js} +1 -1
- package/packages/ui/dist/assets/{network-placeholder-CdhxMzqd.js → network-placeholder-_dLCK4xB.js} +1 -1
- package/packages/ui/dist/assets/{nftPlaceholder-DVmTWEAY.js → nftPlaceholder-Dz4HKEr4.js} +1 -1
- package/packages/ui/dist/assets/{off-DNYLughs.js → off-B1k1lhkr.js} +1 -1
- package/packages/ui/dist/assets/{parseSignature-Dq2B5Bu3.js → parseSignature-Cr0ptV2X.js} +1 -1
- package/packages/ui/dist/assets/{play-store-D7Qut5ta.js → play-store-lYqe4eeL.js} +1 -1
- package/packages/ui/dist/assets/{plus-kqMyjt3q.js → plus-QMgh1krr.js} +1 -1
- package/packages/ui/dist/assets/{qr-code-DiUCWRbz.js → qr-code-ClVHbZWN.js} +1 -1
- package/packages/ui/dist/assets/{recycle-horizontal-Boe3XiS-.js → recycle-horizontal-CxGYnWid.js} +1 -1
- package/packages/ui/dist/assets/{refresh-CrBgBQYO.js → refresh-CPysMza_.js} +1 -1
- package/packages/ui/dist/assets/{reown-logo-CFZCCHSx.js → reown-logo-OoL_zJd0.js} +1 -1
- package/packages/ui/dist/assets/{search-ChTDrghU.js → search-2GaRbf1I.js} +1 -1
- package/packages/ui/dist/assets/{secp256k1-DAV5Q_FR.js → secp256k1-BrB8qSSy.js} +1 -1
- package/packages/ui/dist/assets/{send-DLFbBFe1.js → send-CC2UuIfD.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontal-BEs3emfG.js → swapHorizontal-BRqYwsqT.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalBold-CC-Hfa7W.js → swapHorizontalBold-Bj0GSRq9.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalMedium-BmR0H8DC.js → swapHorizontalMedium-oLOjpU2A.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalRoundedBold-BdP5NGIH.js → swapHorizontalRoundedBold-TJ652QXb.js} +1 -1
- package/packages/ui/dist/assets/{swapVertical-CPrGEJPY.js → swapVertical-e0NLyV3x.js} +1 -1
- package/packages/ui/dist/assets/{telegram-CxNoZ80Q.js → telegram-WhJHVeoU.js} +1 -1
- package/packages/ui/dist/assets/{three-dots-BRa6SBpL.js → three-dots-BlBAOyW-.js} +1 -1
- package/packages/ui/dist/assets/{twitch-BC338bG5.js → twitch-BH7vWmPc.js} +1 -1
- package/packages/ui/dist/assets/{twitterIcon-BGZmt2i9.js → twitterIcon-As0Nkanp.js} +1 -1
- package/packages/ui/dist/assets/{verify-CEstW0zw.js → verify-BEJ0QuLl.js} +1 -1
- package/packages/ui/dist/assets/{verify-filled-OkZb0weU.js → verify-filled-B8Ww2N7z.js} +1 -1
- package/packages/ui/dist/assets/{w3m-modal-pS09ECwE.js → w3m-modal-5rOSZgOR.js} +1 -1
- package/packages/ui/dist/assets/{wallet-BXVKCgC9.js → wallet-CAfC3aml.js} +1 -1
- package/packages/ui/dist/assets/{wallet-placeholder-C_kNhB1c.js → wallet-placeholder-4RZI464Z.js} +1 -1
- package/packages/ui/dist/assets/{walletconnect-CRKIuUHH.js → walletconnect-BiltKqAe.js} +1 -1
- package/packages/ui/dist/assets/{warning-circle-DB2NnwlJ.js → warning-circle-CI4jqpHo.js} +1 -1
- package/packages/ui/dist/assets/{x-DT4RmwL5.js → x-FBttjBWO.js} +1 -1
- package/packages/ui/dist/index.html +14 -14
- package/scripts/check-docs.mjs +262 -262
- package/scripts/check-init.mjs +108 -108
- package/templates/custom-mandate/.sail/contracts/interfaces/IPermission.sol +18 -18
- package/templates/custom-mandate/README.md +116 -116
- package/templates/custom-mandate/foundry.toml +8 -8
- package/templates/custom-mandate/mandates/BoundedCallPermission.sol +41 -41
- package/templates/custom-mandate/mandates/README.md +16 -16
- package/templates/custom-mandate/mandates/SailCalldata.sol +118 -118
- package/templates/default/.cursor/rules +25 -25
- package/templates/default/.env.example +20 -20
- package/templates/default/.github/workflows/agent-tick.yml +33 -33
- package/templates/default/.sail/README.md +13 -13
- package/templates/default/.sail/config.json +10 -10
- package/templates/default/AGENTS.md +171 -171
- package/templates/default/CLAUDE.md +2 -2
- package/templates/default/README.md +16 -16
- package/templates/default/_gitignore +13 -13
- package/templates/default/docs/PERMISSION_MODEL.md +93 -93
- package/templates/default/examples/dca/README.md +16 -16
- package/templates/default/examples/dca/agent.ts +174 -174
- package/templates/default/examples/dca/mandate.ts +45 -45
- package/templates/default/package.json +17 -17
- package/templates/default/src/agent.ts +37 -37
- package/templates/default/src/config.ts +24 -24
- package/templates/default/src/mandate.ts +22 -22
- package/templates/default/tsconfig.json +17 -17
- package/templates/default/ui/README.md +3 -3
- package/templates/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +84 -84
- package/templates/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +97 -97
- package/templates/lifi-permissions/README.md +53 -53
- package/packages/ui/dist/assets/cursor-BDvw-B17.js +0 -3
- package/packages/ui/dist/assets/index-BUhrHLpY.js +0 -1775
- package/packages/ui/dist/assets/index-Cq02kQmy.css +0 -1
- package/scripts/postinstall.js +0 -81
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.26;
|
|
3
|
-
|
|
4
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
-
// Protocol : GMX V2 (gmx-synthetics)
|
|
6
|
-
// Version : ExchangeRouter / OrderHandler — fully on-chain oracle execution
|
|
7
|
-
// NOT Hyperliquid (off-chain order book — permissions cannot bound orders)
|
|
8
|
-
// Chain : Arbitrum mainnet (42161)
|
|
9
|
-
//
|
|
10
|
-
// ⚠ REFERENCE PATTERN — VERIFY SELECTOR, STRUCT, AND ROUTER AGAINST THE LIVE GMX ABI ⚠
|
|
11
|
-
// GMX runs MULTIPLE versioned ExchangeRouter deployments on Arbitrum (e.g.
|
|
12
|
-
// 0x7c68c7866a64fa2160f78eeae12217ffbf871fa8, 0x602b805EedddBbD9ddff44A7dcBD46cb07849685,
|
|
13
|
-
// and others) and HAS EVOLVED the CreateOrderParams struct over time (it added
|
|
14
|
-
// `cancellationReceiver` to the addresses tuple and a trailing `dataList` bytes32[]).
|
|
15
|
-
// The struct + selector below are taken from the CURRENT canonical source
|
|
16
|
-
// (gmx-io/gmx-synthetics, contracts/order/IBaseOrderUtils.sol, main branch) and are
|
|
17
|
-
// mutually consistent — but the specific router YOU target may run an OLDER struct
|
|
18
|
-
// with a DIFFERENT selector. Selector mismatch ⇒ evaluate() returns false for every
|
|
19
|
-
// legitimate order (fail-closed: safe, but the permission silently does nothing useful).
|
|
20
|
-
// Before deploying you MUST:
|
|
21
|
-
// 1. Pick the exact ExchangeRouter your agent will call and read its verified ABI.
|
|
22
|
-
// 2. Confirm its createOrder selector == SEL_CREATE_ORDER below (recompute with
|
|
23
|
-
// `cast sig "createOrder(<exact tuple>)"`); if not, update SEL_CREATE_ORDER and
|
|
24
|
-
// the inline struct to match that router's version.
|
|
25
|
-
// 3. Set EXCHANGE_ROUTER (constructor arg) to that same router address.
|
|
26
|
-
//
|
|
27
|
-
// ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
|
|
28
|
-
// createOrder(IBaseOrderUtils.CreateOrderParams) selector 0x212234c3 (current canonical struct)
|
|
29
|
-
// • target must be EXCHANGE_ROUTER
|
|
30
|
-
// • market must be in ALLOWED_MARKETS
|
|
31
|
-
// • initialCollateralDeltaAmount ≤ MAX_COLLATERAL_AMOUNT
|
|
32
|
-
// • sizeDeltaUsd ≤ MAX_SIZE_DELTA_USD
|
|
33
|
-
// • isLong must be allowed (ALLOW_LONG / ALLOW_SHORT)
|
|
34
|
-
//
|
|
35
|
-
// AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
|
|
36
|
-
// • Leverage ratio: sizeDeltaUsd vs collateralDeltaAmount are bounded separately
|
|
37
|
-
// but their ratio (effective leverage) is not enforced — it depends on collateral price.
|
|
38
|
-
// • acceptablePrice / triggerPrice: not bounded — agent controls entry price.
|
|
39
|
-
// • decreasePositionSwapType, shouldUnwrapNativeToken, autoCancel: not bounded.
|
|
40
|
-
// • swapPath (inside the addresses tuple): not bounded — any intermediate tokens allowed.
|
|
41
|
-
// • receiver / cancellationReceiver: not bounded — set to ctx.account in your agent.
|
|
42
|
-
//
|
|
43
|
-
// VERIFY BEFORE USE:
|
|
44
|
-
// • SEL_CREATE_ORDER = 0x212234c3 was computed (via `cast sig`) from the CURRENT canonical
|
|
45
|
-
// tuple. Older routers differ — see the loud banner above. ALWAYS reconfirm.
|
|
46
|
-
// • sizeDeltaUsd is in USD with 30 decimals (GMX V2 standard). E.g. $1000 = 1e33.
|
|
47
|
-
// • initialCollateralDeltaAmount is in collateral token base units.
|
|
48
|
-
// • Test with real calldata samples from your chosen GMX router before mainnet.
|
|
49
|
-
// • Source: https://github.com/gmx-io/gmx-synthetics/blob/main/contracts/order/IBaseOrderUtils.sol
|
|
50
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
-
|
|
52
|
-
import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
|
|
53
|
-
|
|
54
|
-
contract BoundedPerp_GMXv2_Arbitrum is IPermission {
|
|
55
|
-
bytes32 private constant DISCRIMINATOR = keccak256("BoundedPerp_GMXv2_Arbitrum");
|
|
56
|
-
|
|
57
|
-
address public immutable EXCHANGE_ROUTER;
|
|
58
|
-
mapping(address => bool) public isAllowedMarket;
|
|
59
|
-
uint256 public immutable MAX_COLLATERAL_AMOUNT;
|
|
60
|
-
/// @dev sizeDeltaUsd uses GMX V2's 30-decimal USD representation. 1 USD = 1e30.
|
|
61
|
-
uint256 public immutable MAX_SIZE_DELTA_USD;
|
|
62
|
-
bool public immutable ALLOW_LONG;
|
|
63
|
-
bool public immutable ALLOW_SHORT;
|
|
64
|
-
|
|
65
|
-
// createOrder(IBaseOrderUtils.CreateOrderParams)
|
|
66
|
-
// Computed via `cast sig` (split across lines for readability — paste as one string in the shell):
|
|
67
|
-
// "createOrder((address,address,address,address,address,address,address[]),"
|
|
68
|
-
// "(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256),"
|
|
69
|
-
// "uint8,uint8,bool,bool,bool,bytes32,bytes32[])"
|
|
70
|
-
// == 0x212234c3
|
|
71
|
-
// ⚠ Older GMX routers use an earlier struct (no cancellationReceiver / no dataList) and a
|
|
72
|
-
// DIFFERENT selector. Reconfirm against your chosen router's ABI — see header banner.
|
|
73
|
-
bytes4 private constant SEL_CREATE_ORDER = 0x212234c3;
|
|
74
|
-
|
|
75
|
-
// ── Inline struct definitions — match the CURRENT canonical IBaseOrderUtils.CreateOrderParams ──
|
|
76
|
-
// Source: https://github.com/gmx-io/gmx-synthetics/blob/main/contracts/order/IBaseOrderUtils.sol
|
|
77
|
-
|
|
78
|
-
struct CreateOrderParamsAddresses {
|
|
79
|
-
address receiver;
|
|
80
|
-
address cancellationReceiver; // added in a later GMX version — present in current struct
|
|
81
|
-
address callbackContract;
|
|
82
|
-
address uiFeeReceiver;
|
|
83
|
-
address market;
|
|
84
|
-
address initialCollateralToken;
|
|
85
|
-
address[] swapPath; // dynamic — makes this struct dynamic
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
struct CreateOrderParamsNumbers {
|
|
89
|
-
uint256 sizeDeltaUsd;
|
|
90
|
-
uint256 initialCollateralDeltaAmount;
|
|
91
|
-
uint256 triggerPrice;
|
|
92
|
-
uint256 acceptablePrice;
|
|
93
|
-
uint256 executionFee;
|
|
94
|
-
uint256 callbackGasLimit;
|
|
95
|
-
uint256 minOutputAmount;
|
|
96
|
-
uint256 validFromTime;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
struct CreateOrderParams {
|
|
100
|
-
CreateOrderParamsAddresses addresses;
|
|
101
|
-
CreateOrderParamsNumbers numbers;
|
|
102
|
-
uint8 orderType;
|
|
103
|
-
uint8 decreasePositionSwapType;
|
|
104
|
-
bool isLong;
|
|
105
|
-
bool shouldUnwrapNativeToken;
|
|
106
|
-
bool autoCancel;
|
|
107
|
-
bytes32 referralCode;
|
|
108
|
-
bytes32[] dataList; // added in a later GMX version — present in current struct
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/// @param exchangeRouter GMX V2 ExchangeRouter address
|
|
112
|
-
/// @param allowedMarkets GMX V2 market addresses the agent may trade
|
|
113
|
-
/// @param maxCollateralAmount Per-order collateral cap in collateral token base units
|
|
114
|
-
/// @param maxSizeDeltaUsd Per-order position size cap in GMX USD (30 decimals)
|
|
115
|
-
/// @param allowLong Whether long orders are permitted
|
|
116
|
-
/// @param allowShort Whether short orders are permitted
|
|
117
|
-
constructor(
|
|
118
|
-
address exchangeRouter,
|
|
119
|
-
address[] memory allowedMarkets,
|
|
120
|
-
uint256 maxCollateralAmount,
|
|
121
|
-
uint256 maxSizeDeltaUsd,
|
|
122
|
-
bool allowLong,
|
|
123
|
-
bool allowShort
|
|
124
|
-
) {
|
|
125
|
-
EXCHANGE_ROUTER = exchangeRouter;
|
|
126
|
-
MAX_COLLATERAL_AMOUNT = maxCollateralAmount;
|
|
127
|
-
MAX_SIZE_DELTA_USD = maxSizeDeltaUsd;
|
|
128
|
-
ALLOW_LONG = allowLong;
|
|
129
|
-
ALLOW_SHORT = allowShort;
|
|
130
|
-
for (uint256 i = 0; i < allowedMarkets.length; i++) {
|
|
131
|
-
isAllowedMarket[allowedMarkets[i]] = true;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
|
|
136
|
-
if (ctx.target != EXCHANGE_ROUTER) return false;
|
|
137
|
-
if (ctx.selector != SEL_CREATE_ORDER) return false;
|
|
138
|
-
if (txData.length < 4) return false;
|
|
139
|
-
|
|
140
|
-
// abi.decode handles the nested dynamic struct (address[] swapPath) correctly.
|
|
141
|
-
// A malformed calldata or struct layout mismatch causes a revert → false (fail closed).
|
|
142
|
-
CreateOrderParams memory p = abi.decode(txData[4:], (CreateOrderParams));
|
|
143
|
-
|
|
144
|
-
if (!isAllowedMarket[p.addresses.market]) return false;
|
|
145
|
-
if (p.numbers.initialCollateralDeltaAmount > MAX_COLLATERAL_AMOUNT) return false;
|
|
146
|
-
if (p.numbers.sizeDeltaUsd > MAX_SIZE_DELTA_USD) return false;
|
|
147
|
-
if (p.isLong && !ALLOW_LONG) return false;
|
|
148
|
-
if (!p.isLong && !ALLOW_SHORT) return false;
|
|
149
|
-
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
|
|
154
|
-
}
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// Protocol : GMX V2 (gmx-synthetics)
|
|
6
|
+
// Version : ExchangeRouter / OrderHandler — fully on-chain oracle execution
|
|
7
|
+
// NOT Hyperliquid (off-chain order book — permissions cannot bound orders)
|
|
8
|
+
// Chain : Arbitrum mainnet (42161)
|
|
9
|
+
//
|
|
10
|
+
// ⚠ REFERENCE PATTERN — VERIFY SELECTOR, STRUCT, AND ROUTER AGAINST THE LIVE GMX ABI ⚠
|
|
11
|
+
// GMX runs MULTIPLE versioned ExchangeRouter deployments on Arbitrum (e.g.
|
|
12
|
+
// 0x7c68c7866a64fa2160f78eeae12217ffbf871fa8, 0x602b805EedddBbD9ddff44A7dcBD46cb07849685,
|
|
13
|
+
// and others) and HAS EVOLVED the CreateOrderParams struct over time (it added
|
|
14
|
+
// `cancellationReceiver` to the addresses tuple and a trailing `dataList` bytes32[]).
|
|
15
|
+
// The struct + selector below are taken from the CURRENT canonical source
|
|
16
|
+
// (gmx-io/gmx-synthetics, contracts/order/IBaseOrderUtils.sol, main branch) and are
|
|
17
|
+
// mutually consistent — but the specific router YOU target may run an OLDER struct
|
|
18
|
+
// with a DIFFERENT selector. Selector mismatch ⇒ evaluate() returns false for every
|
|
19
|
+
// legitimate order (fail-closed: safe, but the permission silently does nothing useful).
|
|
20
|
+
// Before deploying you MUST:
|
|
21
|
+
// 1. Pick the exact ExchangeRouter your agent will call and read its verified ABI.
|
|
22
|
+
// 2. Confirm its createOrder selector == SEL_CREATE_ORDER below (recompute with
|
|
23
|
+
// `cast sig "createOrder(<exact tuple>)"`); if not, update SEL_CREATE_ORDER and
|
|
24
|
+
// the inline struct to match that router's version.
|
|
25
|
+
// 3. Set EXCHANGE_ROUTER (constructor arg) to that same router address.
|
|
26
|
+
//
|
|
27
|
+
// ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
|
|
28
|
+
// createOrder(IBaseOrderUtils.CreateOrderParams) selector 0x212234c3 (current canonical struct)
|
|
29
|
+
// • target must be EXCHANGE_ROUTER
|
|
30
|
+
// • market must be in ALLOWED_MARKETS
|
|
31
|
+
// • initialCollateralDeltaAmount ≤ MAX_COLLATERAL_AMOUNT
|
|
32
|
+
// • sizeDeltaUsd ≤ MAX_SIZE_DELTA_USD
|
|
33
|
+
// • isLong must be allowed (ALLOW_LONG / ALLOW_SHORT)
|
|
34
|
+
//
|
|
35
|
+
// AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
|
|
36
|
+
// • Leverage ratio: sizeDeltaUsd vs collateralDeltaAmount are bounded separately
|
|
37
|
+
// but their ratio (effective leverage) is not enforced — it depends on collateral price.
|
|
38
|
+
// • acceptablePrice / triggerPrice: not bounded — agent controls entry price.
|
|
39
|
+
// • decreasePositionSwapType, shouldUnwrapNativeToken, autoCancel: not bounded.
|
|
40
|
+
// • swapPath (inside the addresses tuple): not bounded — any intermediate tokens allowed.
|
|
41
|
+
// • receiver / cancellationReceiver: not bounded — set to ctx.account in your agent.
|
|
42
|
+
//
|
|
43
|
+
// VERIFY BEFORE USE:
|
|
44
|
+
// • SEL_CREATE_ORDER = 0x212234c3 was computed (via `cast sig`) from the CURRENT canonical
|
|
45
|
+
// tuple. Older routers differ — see the loud banner above. ALWAYS reconfirm.
|
|
46
|
+
// • sizeDeltaUsd is in USD with 30 decimals (GMX V2 standard). E.g. $1000 = 1e33.
|
|
47
|
+
// • initialCollateralDeltaAmount is in collateral token base units.
|
|
48
|
+
// • Test with real calldata samples from your chosen GMX router before mainnet.
|
|
49
|
+
// • Source: https://github.com/gmx-io/gmx-synthetics/blob/main/contracts/order/IBaseOrderUtils.sol
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
|
|
53
|
+
|
|
54
|
+
contract BoundedPerp_GMXv2_Arbitrum is IPermission {
|
|
55
|
+
bytes32 private constant DISCRIMINATOR = keccak256("BoundedPerp_GMXv2_Arbitrum");
|
|
56
|
+
|
|
57
|
+
address public immutable EXCHANGE_ROUTER;
|
|
58
|
+
mapping(address => bool) public isAllowedMarket;
|
|
59
|
+
uint256 public immutable MAX_COLLATERAL_AMOUNT;
|
|
60
|
+
/// @dev sizeDeltaUsd uses GMX V2's 30-decimal USD representation. 1 USD = 1e30.
|
|
61
|
+
uint256 public immutable MAX_SIZE_DELTA_USD;
|
|
62
|
+
bool public immutable ALLOW_LONG;
|
|
63
|
+
bool public immutable ALLOW_SHORT;
|
|
64
|
+
|
|
65
|
+
// createOrder(IBaseOrderUtils.CreateOrderParams)
|
|
66
|
+
// Computed via `cast sig` (split across lines for readability — paste as one string in the shell):
|
|
67
|
+
// "createOrder((address,address,address,address,address,address,address[]),"
|
|
68
|
+
// "(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256),"
|
|
69
|
+
// "uint8,uint8,bool,bool,bool,bytes32,bytes32[])"
|
|
70
|
+
// == 0x212234c3
|
|
71
|
+
// ⚠ Older GMX routers use an earlier struct (no cancellationReceiver / no dataList) and a
|
|
72
|
+
// DIFFERENT selector. Reconfirm against your chosen router's ABI — see header banner.
|
|
73
|
+
bytes4 private constant SEL_CREATE_ORDER = 0x212234c3;
|
|
74
|
+
|
|
75
|
+
// ── Inline struct definitions — match the CURRENT canonical IBaseOrderUtils.CreateOrderParams ──
|
|
76
|
+
// Source: https://github.com/gmx-io/gmx-synthetics/blob/main/contracts/order/IBaseOrderUtils.sol
|
|
77
|
+
|
|
78
|
+
struct CreateOrderParamsAddresses {
|
|
79
|
+
address receiver;
|
|
80
|
+
address cancellationReceiver; // added in a later GMX version — present in current struct
|
|
81
|
+
address callbackContract;
|
|
82
|
+
address uiFeeReceiver;
|
|
83
|
+
address market;
|
|
84
|
+
address initialCollateralToken;
|
|
85
|
+
address[] swapPath; // dynamic — makes this struct dynamic
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
struct CreateOrderParamsNumbers {
|
|
89
|
+
uint256 sizeDeltaUsd;
|
|
90
|
+
uint256 initialCollateralDeltaAmount;
|
|
91
|
+
uint256 triggerPrice;
|
|
92
|
+
uint256 acceptablePrice;
|
|
93
|
+
uint256 executionFee;
|
|
94
|
+
uint256 callbackGasLimit;
|
|
95
|
+
uint256 minOutputAmount;
|
|
96
|
+
uint256 validFromTime;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
struct CreateOrderParams {
|
|
100
|
+
CreateOrderParamsAddresses addresses;
|
|
101
|
+
CreateOrderParamsNumbers numbers;
|
|
102
|
+
uint8 orderType;
|
|
103
|
+
uint8 decreasePositionSwapType;
|
|
104
|
+
bool isLong;
|
|
105
|
+
bool shouldUnwrapNativeToken;
|
|
106
|
+
bool autoCancel;
|
|
107
|
+
bytes32 referralCode;
|
|
108
|
+
bytes32[] dataList; // added in a later GMX version — present in current struct
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// @param exchangeRouter GMX V2 ExchangeRouter address
|
|
112
|
+
/// @param allowedMarkets GMX V2 market addresses the agent may trade
|
|
113
|
+
/// @param maxCollateralAmount Per-order collateral cap in collateral token base units
|
|
114
|
+
/// @param maxSizeDeltaUsd Per-order position size cap in GMX USD (30 decimals)
|
|
115
|
+
/// @param allowLong Whether long orders are permitted
|
|
116
|
+
/// @param allowShort Whether short orders are permitted
|
|
117
|
+
constructor(
|
|
118
|
+
address exchangeRouter,
|
|
119
|
+
address[] memory allowedMarkets,
|
|
120
|
+
uint256 maxCollateralAmount,
|
|
121
|
+
uint256 maxSizeDeltaUsd,
|
|
122
|
+
bool allowLong,
|
|
123
|
+
bool allowShort
|
|
124
|
+
) {
|
|
125
|
+
EXCHANGE_ROUTER = exchangeRouter;
|
|
126
|
+
MAX_COLLATERAL_AMOUNT = maxCollateralAmount;
|
|
127
|
+
MAX_SIZE_DELTA_USD = maxSizeDeltaUsd;
|
|
128
|
+
ALLOW_LONG = allowLong;
|
|
129
|
+
ALLOW_SHORT = allowShort;
|
|
130
|
+
for (uint256 i = 0; i < allowedMarkets.length; i++) {
|
|
131
|
+
isAllowedMarket[allowedMarkets[i]] = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
|
|
136
|
+
if (ctx.target != EXCHANGE_ROUTER) return false;
|
|
137
|
+
if (ctx.selector != SEL_CREATE_ORDER) return false;
|
|
138
|
+
if (txData.length < 4) return false;
|
|
139
|
+
|
|
140
|
+
// abi.decode handles the nested dynamic struct (address[] swapPath) correctly.
|
|
141
|
+
// A malformed calldata or struct layout mismatch causes a revert → false (fail closed).
|
|
142
|
+
CreateOrderParams memory p = abi.decode(txData[4:], (CreateOrderParams));
|
|
143
|
+
|
|
144
|
+
if (!isAllowedMarket[p.addresses.market]) return false;
|
|
145
|
+
if (p.numbers.initialCollateralDeltaAmount > MAX_COLLATERAL_AMOUNT) return false;
|
|
146
|
+
if (p.numbers.sizeDeltaUsd > MAX_SIZE_DELTA_USD) return false;
|
|
147
|
+
if (p.isLong && !ALLOW_LONG) return false;
|
|
148
|
+
if (!p.isLong && !ALLOW_SHORT) return false;
|
|
149
|
+
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
|
|
154
|
+
}
|
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.26;
|
|
3
|
-
|
|
4
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
-
// Protocol : Venice (VVV) staking
|
|
6
|
-
// Version : sVVV staking contract (proxy) — fully on-chain
|
|
7
|
-
// Chain : Base mainnet (8453)
|
|
8
|
-
// Target : Staking proxy 0x321b7ff75154472B18EDb199033fF4D116F340Ff ("Staked Venice Token")
|
|
9
|
-
// (impl 0xe37a7920dbc11253ac6d031c29f592f71b348dca — proxy is the stable target)
|
|
10
|
-
// Staked asset: VVV 0xacfE6019Ed1A7Dc6f7B508C02d1b04ec88cC21bf
|
|
11
|
-
//
|
|
12
|
-
// ⚠ SELECTOR NOTE — the function is stake(address,uint256), NOT stake(uint256).
|
|
13
|
-
// Verified against the live contract (bytecode + 4byte registry): the staking entrypoint is
|
|
14
|
-
// stake(address recipient, uint256 amount) = 0xadc9772e. The single-arg stake(uint256)
|
|
15
|
-
// = 0xa694fc3a is ABSENT. Gating the wrong selector silently rejects every real stake
|
|
16
|
-
// (fail-closed but non-functional). ALWAYS confirm the selector against the contract you target.
|
|
17
|
-
//
|
|
18
|
-
// ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
|
|
19
|
-
// stake(address recipient,uint256 amount) selector 0xadc9772e
|
|
20
|
-
// • target must be STAKING_CONTRACT
|
|
21
|
-
// • amount ≤ MAX_STAKE_AMOUNT
|
|
22
|
-
// • recipient must equal ctx.account (the SMA — the staked position cannot be assigned
|
|
23
|
-
// to another address; verified empirically: this arg is the position beneficiary)
|
|
24
|
-
// claim() selector 0x4e71d92d
|
|
25
|
-
// • target must be STAKING_CONTRACT
|
|
26
|
-
// • takes NO recipient argument — rewards always accrue to the caller (the SMA when the
|
|
27
|
-
// kernel dispatches), so "claim-rewards-to-SMA-only" holds structurally
|
|
28
|
-
//
|
|
29
|
-
// AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
|
|
30
|
-
// • Stake timing / cadence and how often rewards are claimed
|
|
31
|
-
// • Unstaking: this permission does NOT allow initiateUnstake(uint256) (0xae5ac921) — add it
|
|
32
|
-
// with its own bounds if the agent should manage exits; left out to keep this single-purpose.
|
|
33
|
-
// • Staked ASSET: enforced TRANSITIVELY via STAKING_CONTRACT (the contract accepts one fixed
|
|
34
|
-
// token, VVV). The stake calldata carries no token field, so the asset is pinned by the target.
|
|
35
|
-
//
|
|
36
|
-
// VERIFY BEFORE USE:
|
|
37
|
-
// • Confirm STAKING_CONTRACT and that its stake selector is 0xadc9772e on the contract you target.
|
|
38
|
-
// • Confirm the stake address arg is the position recipient (not, e.g., a token address) on your
|
|
39
|
-
// contract — on Venice it is the recipient (verified by staking to a third party on a fork).
|
|
40
|
-
// • MAX_STAKE_AMOUNT is in VVV base units (18 decimals).
|
|
41
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
|
|
44
|
-
|
|
45
|
-
contract BoundedStake_Venice_Base is IPermission {
|
|
46
|
-
bytes32 private constant DISCRIMINATOR = keccak256("BoundedStake_Venice_Base");
|
|
47
|
-
|
|
48
|
-
address public immutable STAKING_CONTRACT;
|
|
49
|
-
uint256 public immutable MAX_STAKE_AMOUNT;
|
|
50
|
-
|
|
51
|
-
// Verified against the live Venice staking contract (bytecode + 4byte registry):
|
|
52
|
-
bytes4 private constant SEL_STAKE = 0xadc9772e; // stake(address recipient,uint256 amount)
|
|
53
|
-
bytes4 private constant SEL_CLAIM = 0x4e71d92d; // claim()
|
|
54
|
-
|
|
55
|
-
/// @param stakingContract Venice staking contract (the proxy address)
|
|
56
|
-
/// @param maxStakeAmount Per-stake cap in VVV base units (18 decimals; must be > 0)
|
|
57
|
-
constructor(address stakingContract, uint256 maxStakeAmount) {
|
|
58
|
-
require(stakingContract != address(0), "zero staking contract");
|
|
59
|
-
require(maxStakeAmount > 0, "zero stake cap");
|
|
60
|
-
STAKING_CONTRACT = stakingContract;
|
|
61
|
-
MAX_STAKE_AMOUNT = maxStakeAmount;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
|
|
65
|
-
if (ctx.target != STAKING_CONTRACT) return false;
|
|
66
|
-
|
|
67
|
-
// stake(address recipient, uint256 amount)
|
|
68
|
-
if (ctx.selector == SEL_STAKE) {
|
|
69
|
-
if (txData.length < 4 + 2 * 32) return false;
|
|
70
|
-
(address recipient, uint256 amount) = abi.decode(txData[4:], (address, uint256));
|
|
71
|
-
if (amount > MAX_STAKE_AMOUNT) return false;
|
|
72
|
-
if (recipient != ctx.account) return false; // stake only to the SMA's own position
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// claim() — no args; rewards go to the caller (the SMA). No recipient to bound.
|
|
77
|
-
if (ctx.selector == SEL_CLAIM) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
|
|
85
|
-
}
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
// Protocol : Venice (VVV) staking
|
|
6
|
+
// Version : sVVV staking contract (proxy) — fully on-chain
|
|
7
|
+
// Chain : Base mainnet (8453)
|
|
8
|
+
// Target : Staking proxy 0x321b7ff75154472B18EDb199033fF4D116F340Ff ("Staked Venice Token")
|
|
9
|
+
// (impl 0xe37a7920dbc11253ac6d031c29f592f71b348dca — proxy is the stable target)
|
|
10
|
+
// Staked asset: VVV 0xacfE6019Ed1A7Dc6f7B508C02d1b04ec88cC21bf
|
|
11
|
+
//
|
|
12
|
+
// ⚠ SELECTOR NOTE — the function is stake(address,uint256), NOT stake(uint256).
|
|
13
|
+
// Verified against the live contract (bytecode + 4byte registry): the staking entrypoint is
|
|
14
|
+
// stake(address recipient, uint256 amount) = 0xadc9772e. The single-arg stake(uint256)
|
|
15
|
+
// = 0xa694fc3a is ABSENT. Gating the wrong selector silently rejects every real stake
|
|
16
|
+
// (fail-closed but non-functional). ALWAYS confirm the selector against the contract you target.
|
|
17
|
+
//
|
|
18
|
+
// ENFORCES ON-CHAIN (kernel calls evaluate() on every dispatch; false ⇒ dispatch blocked):
|
|
19
|
+
// stake(address recipient,uint256 amount) selector 0xadc9772e
|
|
20
|
+
// • target must be STAKING_CONTRACT
|
|
21
|
+
// • amount ≤ MAX_STAKE_AMOUNT
|
|
22
|
+
// • recipient must equal ctx.account (the SMA — the staked position cannot be assigned
|
|
23
|
+
// to another address; verified empirically: this arg is the position beneficiary)
|
|
24
|
+
// claim() selector 0x4e71d92d
|
|
25
|
+
// • target must be STAKING_CONTRACT
|
|
26
|
+
// • takes NO recipient argument — rewards always accrue to the caller (the SMA when the
|
|
27
|
+
// kernel dispatches), so "claim-rewards-to-SMA-only" holds structurally
|
|
28
|
+
//
|
|
29
|
+
// AGENT-ENFORCED / NOT BOUNDED HERE (off-chain — can change without redeploying this contract):
|
|
30
|
+
// • Stake timing / cadence and how often rewards are claimed
|
|
31
|
+
// • Unstaking: this permission does NOT allow initiateUnstake(uint256) (0xae5ac921) — add it
|
|
32
|
+
// with its own bounds if the agent should manage exits; left out to keep this single-purpose.
|
|
33
|
+
// • Staked ASSET: enforced TRANSITIVELY via STAKING_CONTRACT (the contract accepts one fixed
|
|
34
|
+
// token, VVV). The stake calldata carries no token field, so the asset is pinned by the target.
|
|
35
|
+
//
|
|
36
|
+
// VERIFY BEFORE USE:
|
|
37
|
+
// • Confirm STAKING_CONTRACT and that its stake selector is 0xadc9772e on the contract you target.
|
|
38
|
+
// • Confirm the stake address arg is the position recipient (not, e.g., a token address) on your
|
|
39
|
+
// contract — on Venice it is the recipient (verified by staking to a third party on a fork).
|
|
40
|
+
// • MAX_STAKE_AMOUNT is in VVV base units (18 decimals).
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
import {IPermission, Context} from "@sail/interfaces/IPermission.sol";
|
|
44
|
+
|
|
45
|
+
contract BoundedStake_Venice_Base is IPermission {
|
|
46
|
+
bytes32 private constant DISCRIMINATOR = keccak256("BoundedStake_Venice_Base");
|
|
47
|
+
|
|
48
|
+
address public immutable STAKING_CONTRACT;
|
|
49
|
+
uint256 public immutable MAX_STAKE_AMOUNT;
|
|
50
|
+
|
|
51
|
+
// Verified against the live Venice staking contract (bytecode + 4byte registry):
|
|
52
|
+
bytes4 private constant SEL_STAKE = 0xadc9772e; // stake(address recipient,uint256 amount)
|
|
53
|
+
bytes4 private constant SEL_CLAIM = 0x4e71d92d; // claim()
|
|
54
|
+
|
|
55
|
+
/// @param stakingContract Venice staking contract (the proxy address)
|
|
56
|
+
/// @param maxStakeAmount Per-stake cap in VVV base units (18 decimals; must be > 0)
|
|
57
|
+
constructor(address stakingContract, uint256 maxStakeAmount) {
|
|
58
|
+
require(stakingContract != address(0), "zero staking contract");
|
|
59
|
+
require(maxStakeAmount > 0, "zero stake cap");
|
|
60
|
+
STAKING_CONTRACT = stakingContract;
|
|
61
|
+
MAX_STAKE_AMOUNT = maxStakeAmount;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function evaluate(bytes calldata txData, Context calldata ctx) external view returns (bool) {
|
|
65
|
+
if (ctx.target != STAKING_CONTRACT) return false;
|
|
66
|
+
|
|
67
|
+
// stake(address recipient, uint256 amount)
|
|
68
|
+
if (ctx.selector == SEL_STAKE) {
|
|
69
|
+
if (txData.length < 4 + 2 * 32) return false;
|
|
70
|
+
(address recipient, uint256 amount) = abi.decode(txData[4:], (address, uint256));
|
|
71
|
+
if (amount > MAX_STAKE_AMOUNT) return false;
|
|
72
|
+
if (recipient != ctx.account) return false; // stake only to the SMA's own position
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// claim() — no args; rewards go to the caller (the SMA). No recipient to bound.
|
|
77
|
+
if (ctx.selector == SEL_CLAIM) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function discriminator() external pure returns (bytes32) { return DISCRIMINATOR; }
|
|
85
|
+
}
|