@dev.sail.money/sailor 1.3.0-98 → 1.4.0-242
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 +7 -7
- package/LICENSE +1 -1
- package/README.md +64 -472
- package/package.json +11 -6
- package/packages/cli/README.md +1 -1
- package/packages/cli/dist/index.cjs +1469 -463
- package/packages/cli/dist/server.cjs +19796 -21737
- package/packages/sdk/README.md +1 -1
- package/packages/sdk/dist/abis/ConfigurablePermission.d.ts +130 -0
- package/packages/sdk/dist/abis/ConfigurablePermission.d.ts.map +1 -0
- package/packages/sdk/dist/abis/ConfigurablePermission.js +79 -0
- package/packages/sdk/dist/abis/ConfigurablePermission.js.map +1 -0
- package/packages/sdk/dist/abis/SailKernel.d.ts +42 -0
- package/packages/sdk/dist/abis/SailKernel.d.ts.map +1 -1
- package/packages/sdk/dist/abis/SailKernel.js +34 -0
- package/packages/sdk/dist/abis/SailKernel.js.map +1 -1
- package/packages/sdk/dist/abis/index.d.ts +1 -0
- package/packages/sdk/dist/abis/index.d.ts.map +1 -1
- package/packages/sdk/dist/abis/index.js +1 -0
- package/packages/sdk/dist/abis/index.js.map +1 -1
- package/packages/sdk/dist/chains.d.ts +0 -7
- package/packages/sdk/dist/chains.d.ts.map +1 -1
- package/packages/sdk/dist/chains.js +81 -32
- package/packages/sdk/dist/chains.js.map +1 -1
- package/packages/sdk/dist/client.d.ts +1 -1
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +137 -16
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/deployments.d.ts +6 -4
- package/packages/sdk/dist/deployments.d.ts.map +1 -1
- package/packages/sdk/dist/deployments.js +120 -118
- package/packages/sdk/dist/deployments.js.map +1 -1
- package/packages/sdk/dist/eip712.d.ts +84 -0
- package/packages/sdk/dist/eip712.d.ts.map +1 -1
- package/packages/sdk/dist/eip712.js +148 -0
- package/packages/sdk/dist/eip712.js.map +1 -1
- package/packages/sdk/dist/fees.d.ts +2 -1
- package/packages/sdk/dist/fees.d.ts.map +1 -1
- package/packages/sdk/dist/fees.js +2 -1
- package/packages/sdk/dist/fees.js.map +1 -1
- package/packages/sdk/dist/index.d.ts +3 -2
- package/packages/sdk/dist/index.d.ts.map +1 -1
- package/packages/sdk/dist/index.js +3 -2
- package/packages/sdk/dist/index.js.map +1 -1
- package/packages/sdk/dist/intelligence.d.ts +1 -1
- package/packages/sdk/dist/intelligence.js +1 -1
- package/packages/sdk/dist/safe.d.ts +49 -0
- package/packages/sdk/dist/safe.d.ts.map +1 -1
- package/packages/sdk/dist/safe.js +74 -0
- package/packages/sdk/dist/safe.js.map +1 -1
- package/packages/sdk/package.json +6 -2
- package/packages/ui/dist/assets/{add-BX5reKtK.js → add-CBbu2pe_.js} +2 -2
- package/packages/ui/dist/assets/{all-wallets-IZ3PlVF-.js → all-wallets-C1oy4NUu.js} +2 -2
- package/packages/ui/dist/assets/{app-store-ARgRdMdF.js → app-store-DKqyf0Zv.js} +1 -1
- package/packages/ui/dist/assets/{apple-VCaqsI9w.js → apple-DyoIbCFy.js} +2 -2
- package/packages/ui/dist/assets/{arrow-bottom-circle-h5vFiZ2I.js → arrow-bottom-circle-CMS-OqZQ.js} +2 -2
- package/packages/ui/dist/assets/arrow-bottom-pUWeEqYu.js +8 -0
- package/packages/ui/dist/assets/arrow-left-B81HMJNL.js +8 -0
- package/packages/ui/dist/assets/arrow-right-Z5czoKlC.js +8 -0
- package/packages/ui/dist/assets/arrow-top-C6ym1oll.js +8 -0
- package/packages/ui/dist/assets/{bank-6c28yOM9.js → bank-B41nlslf.js} +2 -2
- package/packages/ui/dist/assets/{basic-CJnbTfSd.js → basic-CWqOb7CV.js} +177 -177
- package/packages/ui/dist/assets/{browser-CDKlxM3D.js → browser-DDKdqoZ9.js} +2 -2
- package/packages/ui/dist/assets/{card-DQdED02q.js → card-BgOmTajq.js} +2 -2
- package/packages/ui/dist/assets/ccip-CtJUKWRd.js +1 -0
- package/packages/ui/dist/assets/{checkmark-JoRNVoFV.js → checkmark-CGqQipH4.js} +2 -2
- package/packages/ui/dist/assets/{checkmark-bold-B6bGHVIs.js → checkmark-bold-Ci5xf90j.js} +2 -2
- package/packages/ui/dist/assets/chevron-bottom-D7Xert17.js +8 -0
- package/packages/ui/dist/assets/chevron-left-BGbUebR2.js +8 -0
- package/packages/ui/dist/assets/chevron-right-xVsESnHU.js +8 -0
- package/packages/ui/dist/assets/chevron-top-DEA9cR6v.js +8 -0
- package/packages/ui/dist/assets/{chrome-store-D-fiJ9qJ.js → chrome-store-BsyI-4PI.js} +2 -2
- package/packages/ui/dist/assets/{clock-xCPkzwjj.js → clock-Dct9oQ3M.js} +2 -2
- package/packages/ui/dist/assets/{close-BkqmPpWG.js → close-BvSCnI2y.js} +2 -2
- package/packages/ui/dist/assets/{coinPlaceholder-PIvN9oq_.js → coinPlaceholder-p7RiWkon.js} +2 -2
- package/packages/ui/dist/assets/{compass-CW5kWcxW.js → compass-BUeeZqX4.js} +2 -2
- package/packages/ui/dist/assets/{copy-DUZlAQJJ.js → copy-C5jXbxLq.js} +2 -2
- package/packages/ui/dist/assets/core-CiAm42Mq.js +907 -0
- package/packages/ui/dist/assets/cursor-C0T7JWFO.js +3 -0
- package/packages/ui/dist/assets/{cursor-transparent-BUAMB0G1.js → cursor-transparent-BZp3-JmA.js} +2 -2
- package/packages/ui/dist/assets/{desktop-BxdlwV8P.js → desktop-BIui_Lzb.js} +2 -2
- package/packages/ui/dist/assets/{disconnect-GelNh3mn.js → disconnect-B3Bxdchp.js} +2 -2
- package/packages/ui/dist/assets/{discord-N0jnxkzC.js → discord-VWI0yDyx.js} +2 -2
- package/packages/ui/dist/assets/{etherscan-B41u7wF4.js → etherscan-BpBvUT9i.js} +2 -2
- package/packages/ui/dist/assets/events-DQ172AOg.js +1 -0
- package/packages/ui/dist/assets/{exclamation-triangle-BWJC56FC.js → exclamation-triangle-o1TJHIUw.js} +2 -2
- package/packages/ui/dist/assets/{extension-G6I6oogw.js → extension-4RaLvbh5.js} +2 -2
- package/packages/ui/dist/assets/external-link-BHIwjDgj.js +8 -0
- package/packages/ui/dist/assets/{facebook-47twy32b.js → facebook-Bes5g-wN.js} +2 -2
- package/packages/ui/dist/assets/{farcaster-DYqnqCqs.js → farcaster-CiJ-352m.js} +2 -2
- package/packages/ui/dist/assets/{filters-dF6xeJnE.js → filters-DMOteumb.js} +2 -2
- package/packages/ui/dist/assets/{github-DbW4Ihz2.js → github-D2nDec9X.js} +2 -2
- package/packages/ui/dist/assets/{google-TKm7G8LF.js → google-lZZlQZPQ.js} +2 -2
- package/packages/ui/dist/assets/{help-circle-CY3qTmsG.js → help-circle-D4cSSziR.js} +1 -1
- package/packages/ui/dist/assets/{id-JpHW4q14.js → id-BmhXZq5P.js} +2 -2
- package/packages/ui/dist/assets/{image-Dip2fG98.js → image-Dz4FPeET.js} +2 -2
- package/packages/ui/dist/assets/index-B07bCKJ8.css +1 -0
- package/packages/ui/dist/assets/index-BV8s6ez1.js +1 -0
- package/packages/ui/dist/assets/index-BeUp3nOq.js +1 -0
- package/packages/ui/dist/assets/index-CvRylmZJ.js +16 -0
- package/packages/ui/dist/assets/{index-q0mGElxZ.js → index-DeoQO45V.js} +3 -3
- package/packages/ui/dist/assets/index-Et5S3ZaW.js +47 -0
- package/packages/ui/dist/assets/index-LiKeH8Cd.js +1780 -0
- package/packages/ui/dist/assets/index.es-dfpEui-9.js +26 -0
- package/packages/ui/dist/assets/{info-B1enFsqh.js → info-77RSvIMg.js} +2 -2
- package/packages/ui/dist/assets/{info-circle-D4j3FISs.js → info-circle-CWsII-NN.js} +2 -2
- package/packages/ui/dist/assets/{lightbulb-DZbKr-vS.js → lightbulb-BZXzm7XE.js} +2 -2
- package/packages/ui/dist/assets/{mail-D-TO_uVQ.js → mail-DMG1YqB0.js} +2 -2
- package/packages/ui/dist/assets/metamask-sdk-K1x_iqcc.js +542 -0
- package/packages/ui/dist/assets/{mobile-Bkmwzav2.js → mobile-DuJfOBSk.js} +2 -2
- package/packages/ui/dist/assets/{more-D1_I_jiB.js → more-Cs9ySK2F.js} +2 -2
- package/packages/ui/dist/assets/{network-placeholder-DUaFjoXh.js → network-placeholder-CMDPmB1E.js} +2 -2
- package/packages/ui/dist/assets/{nftPlaceholder-Cc0iUryh.js → nftPlaceholder-Cj0f9y2u.js} +2 -2
- package/packages/ui/dist/assets/{off-BvX71sse.js → off-9zSSsm-e.js} +2 -2
- package/packages/ui/dist/assets/{play-store-XzLbtbTd.js → play-store-CPwIb8Gj.js} +2 -2
- package/packages/ui/dist/assets/{plus-B77_RmYR.js → plus-BOHQri8o.js} +2 -2
- package/packages/ui/dist/assets/{qr-code-BNeIhB1d.js → qr-code-DqhbK276.js} +1 -1
- package/packages/ui/dist/assets/{recycle-horizontal-B9sX-yty.js → recycle-horizontal-BpGM56Qz.js} +2 -2
- package/packages/ui/dist/assets/{refresh-BWRygnjm.js → refresh-BPzZlhTP.js} +2 -2
- package/packages/ui/dist/assets/{reown-logo-DCAEsGFm.js → reown-logo-OCkI0xzo.js} +2 -2
- package/packages/ui/dist/assets/{search-ZXIqrawS.js → search-B1JEJvwq.js} +2 -2
- package/packages/ui/dist/assets/{secp256k1-Doz05h4g.js → secp256k1-BqUeV9Cb.js} +1 -1
- package/packages/ui/dist/assets/{send-BQcO0jNC.js → send-JNsDlNoq.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontal-4yZEzz2K.js → swapHorizontal-C3Z1FImK.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontalBold-DxBKu3sl.js → swapHorizontalBold-Cmdy2iAn.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontalMedium-DAQYJ2JK.js → swapHorizontalMedium-DFX57Nn9.js} +2 -2
- package/packages/ui/dist/assets/{swapHorizontalRoundedBold-CUlZzFJ6.js → swapHorizontalRoundedBold-baB389c8.js} +2 -2
- package/packages/ui/dist/assets/{swapVertical-CjWHAaDM.js → swapVertical-DmAYGfK1.js} +2 -2
- package/packages/ui/dist/assets/{telegram-JBeabiJP.js → telegram-BHbYE-nI.js} +2 -2
- package/packages/ui/dist/assets/{three-dots-D3R8OjSx.js → three-dots-BwRWl6qJ.js} +2 -2
- package/packages/ui/dist/assets/{twitch-1f9z7xVO.js → twitch-Drx0q6B6.js} +2 -2
- package/packages/ui/dist/assets/{twitterIcon-BW7n2xnp.js → twitterIcon-AuJ2KQ6p.js} +2 -2
- package/packages/ui/dist/assets/{verify-Cs5owI38.js → verify-Dkrd3q7o.js} +2 -2
- package/packages/ui/dist/assets/{verify-filled-Ci5sx5qS.js → verify-filled-B9e3-nz-.js} +2 -2
- package/packages/ui/dist/assets/{w3m-modal-DnWtss8u.js → w3m-modal-DtkEjQA7.js} +1 -1
- package/packages/ui/dist/assets/{wallet-MaGNqaW-.js → wallet-CqYzSiBd.js} +2 -2
- package/packages/ui/dist/assets/{wallet-placeholder-kTOy12_L.js → wallet-placeholder-B05kTN_K.js} +2 -2
- package/packages/ui/dist/assets/{walletconnect-CyuP92jg.js → walletconnect-BwoPXLaS.js} +3 -3
- package/packages/ui/dist/assets/{warning-circle-BImUCdpG.js → warning-circle-DK9Ai8rT.js} +2 -2
- package/packages/ui/dist/assets/{x-II45JHpV.js → x-BNdh5DVW.js} +2 -2
- package/packages/ui/dist/index.html +2 -2
- package/templates/default/.agents/skills/sail-mandates/SKILL.md +1 -1
- package/templates/default/.agents/skills/sail-mandates/references/approvals.md +3 -3
- package/templates/default/.agents/skills/sail-mandates/references/examples-index.md +3 -3
- package/templates/default/.agents/skills/sail-onboarding/SKILL.md +34 -33
- package/templates/default/.agents/skills/sail-servers/SKILL.md +1 -0
- package/templates/default/.agents/skills/sail-swap-quote/SKILL.md +76 -0
- package/templates/default/.agents/skills/sail-template-approve-batch/SKILL.md +98 -0
- package/templates/default/.agents/skills/sail-template-borrow/SKILL.md +68 -0
- package/templates/default/.agents/skills/sail-template-deposit/SKILL.md +61 -0
- package/templates/default/.agents/skills/sail-template-swap/SKILL.md +207 -0
- package/templates/default/.agents/skills/sail-template-swap-no-oracle/SKILL.md +100 -0
- package/templates/default/.agents/skills/sail-template-transfer/SKILL.md +52 -0
- package/templates/default/.agents/skills/sail-template-withdraw/SKILL.md +50 -0
- package/templates/default/.agents/skills/sail-templates/SKILL.md +142 -0
- package/templates/default/.agents/skills/sail-templates/catalog.mjs +200 -0
- package/templates/default/.agents/skills/sail-templates/deployed.json +133 -0
- package/templates/default/.agents/skills/sail-templates/oracles/IOracle.sol +13 -0
- package/templates/default/.agents/skills/sail-templates/oracles/UniV3TwapOracle.sol +114 -0
- package/templates/default/.agents/skills/sail-templates/oracles/libs/FullMath.sol +62 -0
- package/templates/default/.agents/skills/sail-templates/oracles/libs/TickMath.sol +44 -0
- package/templates/default/.agents/skills/sail-templates/references/config-schemas.md +139 -0
- package/templates/default/.agents/skills/sail-templates/references/reuse-flow.md +139 -0
- package/templates/default/.agents/skills/sail-token-resolve/SKILL.md +174 -0
- package/templates/default/.env.example +9 -0
- package/templates/default/AGENTS.md +107 -13
- package/templates/default/docs/PERMISSION_MODEL.md +2 -2
- package/{examples → templates/default/examples}/custom-mandate/.sail/contracts/interfaces/IPermission.sol +4 -0
- package/{examples → templates/default/examples}/custom-mandate/README.md +4 -1
- package/{examples → templates/default/examples}/permissions/interfaces/IBatchPermission.sol +2 -0
- package/{examples → templates/default/examples}/permissions/interfaces/IPermission.sol +4 -0
- package/templates/default/scripts/quote-swap.mjs +211 -0
- package/templates/default/scripts/resolve-token.mjs +992 -0
- package/templates/default/scripts/shared-template-addr.mjs +110 -0
- package/templates/default/test/BoundedCallPermission.t.sol +2 -1
- package/docs/PERMISSION_MODEL.md +0 -93
- package/examples/README.md +0 -24
- package/examples/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +0 -84
- package/examples/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +0 -97
- package/examples/lifi-permissions/README.md +0 -53
- package/packages/ui/dist/assets/arrow-bottom-Cwptw8fW.js +0 -8
- package/packages/ui/dist/assets/arrow-left-bM1TRcV9.js +0 -8
- package/packages/ui/dist/assets/arrow-right-BYd_N4Jr.js +0 -8
- package/packages/ui/dist/assets/arrow-top-CLUnwVE2.js +0 -8
- package/packages/ui/dist/assets/ccip-BL7RIPfY.js +0 -1
- package/packages/ui/dist/assets/chevron-bottom-BMCelIEu.js +0 -8
- package/packages/ui/dist/assets/chevron-left-ryPLhHCc.js +0 -8
- package/packages/ui/dist/assets/chevron-right-CdDNRv7f.js +0 -8
- package/packages/ui/dist/assets/chevron-top-BxWIjjGk.js +0 -8
- package/packages/ui/dist/assets/core-von2c1sc.js +0 -907
- package/packages/ui/dist/assets/cursor-BzRVY7BD.js +0 -3
- package/packages/ui/dist/assets/events-C9icG2YB.js +0 -1
- package/packages/ui/dist/assets/external-link-BPaGQf4a.js +0 -8
- package/packages/ui/dist/assets/fallback-BTfJeGjh.js +0 -1
- package/packages/ui/dist/assets/index-BGJ_8fMk.js +0 -1
- package/packages/ui/dist/assets/index-Bq3PAopp.js +0 -16
- package/packages/ui/dist/assets/index-C4_cA5XW.js +0 -1
- package/packages/ui/dist/assets/index-CaDN0gL4.js +0 -1
- package/packages/ui/dist/assets/index-DLElIcBr.js +0 -1789
- package/packages/ui/dist/assets/index-lhNfD4jA.css +0 -1
- package/packages/ui/dist/assets/index.es-BaCKS_tW.js +0 -26
- package/packages/ui/dist/assets/metamask-sdk-DljW3QD1.js +0 -542
- package/packages/ui/dist/assets/native-CJ5et6AR.js +0 -1
- package/packages/ui/dist/assets/parseSignature-BAS-wKoj.js +0 -1
- package/scripts/check-docs.mjs +0 -309
- package/scripts/check-init.mjs +0 -123
- package/scripts/check-update.mjs +0 -177
- package/scripts/clean.mjs +0 -17
- package/scripts/pack.mjs +0 -21
- package/templates/default/.agents/skills/sail-overview/SKILL.md +0 -100
- package/templates/default/.agents/skills/sail-sailor/SKILL.md +0 -190
- package/templates/default/.agents/skills/sail-setup/SKILL.md +0 -38
- package/templates/default/.gitlab-ci.yml +0 -58
- /package/{examples → templates/default/examples}/custom-mandate/foundry.toml +0 -0
- /package/{examples → templates/default/examples}/custom-mandate/mandates/BoundedCallPermission.sol +0 -0
- /package/{examples → templates/default/examples}/custom-mandate/mandates/README.md +0 -0
- /package/{examples → templates/default/examples}/custom-mandate/mandates/SailCalldata.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedApproveAndCallBatch.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedLimitless_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedPerp_GMXv2_Arbitrum.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedStake_Venice_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedSupply_AaveV3_Arbitrum.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedSwapNative_UniswapV3_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV3_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedSwap_UniswapV4_Unichain.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedTransfer_ERC20_Ethereum.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/BoundedVault_ERC4626_Base.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/README.md +0 -0
- /package/{examples → templates/default/examples}/permissions/SailCalldata.sol +0 -0
- /package/{examples → templates/default/examples}/permissions/foundry.toml +0 -0
|
@@ -36,11 +36,14 @@ struct Context {
|
|
|
36
36
|
uint256 value; // msg.value (native asset) sent with the call
|
|
37
37
|
uint256 blockTimestamp; // block.timestamp at evaluation
|
|
38
38
|
uint256 blockNumber; // block.number at evaluation
|
|
39
|
+
uint256 configEpoch; // kernel registrationEpoch(account, permission) at dispatch;
|
|
40
|
+
// ignore it unless your permission takes post-deploy configuration
|
|
39
41
|
}
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
- `evaluate` — your policy. Return `true` to permit the call, `false` to block it. Runs under a
|
|
43
|
-
|
|
45
|
+
150,000-gas `staticcall` (`SailKernel.PERMISSION_GAS_CAP`); a revert or gas overage is treated
|
|
46
|
+
as `false`.
|
|
44
47
|
- `discriminator` — a stable `bytes32` name for your permission (e.g. `keccak256("MyMandate")`).
|
|
45
48
|
|
|
46
49
|
Keep all policy parameters constructor-configured so each deployment is a complete, reviewable
|
|
@@ -26,6 +26,8 @@ struct BatchContext {
|
|
|
26
26
|
bytes32 batchHash; // keccak256(abi.encode(calls)) — stable id for the exact sequence
|
|
27
27
|
uint256 blockTimestamp; // block.timestamp at dispatch
|
|
28
28
|
uint256 blockNumber; // block.number at dispatch
|
|
29
|
+
uint256 configEpoch; // kernel registrationEpoch(account, permission) at dispatch —
|
|
30
|
+
// same fail-closed freshness check as Context.configEpoch
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
interface IBatchPermission {
|
|
@@ -10,6 +10,10 @@ struct Context {
|
|
|
10
10
|
uint256 value;
|
|
11
11
|
uint256 blockTimestamp;
|
|
12
12
|
uint256 blockNumber;
|
|
13
|
+
/// @dev Kernel registrationEpoch(account, permission) at dispatch. Configurable
|
|
14
|
+
/// permissions fail closed on a mismatch with the epoch stamped at configure()
|
|
15
|
+
/// time; permissions without post-deploy configuration can ignore it.
|
|
16
|
+
uint256 configEpoch;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
interface IPermission {
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// quote-swap.mjs — live Uniswap V3 quote via QuoterV2 + an amountOutMinimum floor.
|
|
3
|
+
//
|
|
4
|
+
// Takes resolved tokens + an exact amount (from resolve-token.mjs), returns the
|
|
5
|
+
// expected output and a slippage-adjusted amountOutMinimum ready to embed in a
|
|
6
|
+
// swap dispatch. Pure JS, no dependencies.
|
|
7
|
+
//
|
|
8
|
+
// node scripts/quote-swap.mjs \
|
|
9
|
+
// --token-in 0x078D...AD6 --decimals-in 6 \
|
|
10
|
+
// --token-out 0x4200...0006 --decimals-out 18 \
|
|
11
|
+
// --amount 25000000 --fee 3000 --slippage-bps 100
|
|
12
|
+
//
|
|
13
|
+
// Output: one JSON object on stdout; human notes on stderr.
|
|
14
|
+
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { resolve as resolvePath } from "node:path";
|
|
17
|
+
|
|
18
|
+
const CHAINS = {
|
|
19
|
+
ethereum: { chainId: 1, quoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e" },
|
|
20
|
+
unichain: { chainId: 130, quoterV2: "0x385a5cf5f83e99f7bb2852b6a19c3538b9fa7658" },
|
|
21
|
+
base: { chainId: 8453, quoterV2: "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a" },
|
|
22
|
+
arbitrum: { chainId: 42161, quoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e" },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const SEL_QUOTE = "0xc6a5026a"; // quoteExactInputSingle((address,address,uint256,uint24,uint160))
|
|
26
|
+
const ADDR_ZERO = "0x" + "0".repeat(40);
|
|
27
|
+
|
|
28
|
+
// ── minimal ABI encode/decode (mirrors resolve-token.mjs) ───────────────────────
|
|
29
|
+
function pad32(h) {
|
|
30
|
+
h = h.toLowerCase().replace(/^0x/, "");
|
|
31
|
+
if (h.length < 64) h = "0".repeat(64 - h.length) + h;
|
|
32
|
+
return h;
|
|
33
|
+
}
|
|
34
|
+
function uintToHex(n) {
|
|
35
|
+
let h = n.toString(16);
|
|
36
|
+
if (h.length % 2) h = "0" + h;
|
|
37
|
+
return pad32(h);
|
|
38
|
+
}
|
|
39
|
+
function encodeQuoteCall(tokenIn, tokenOut, amountIn, fee) {
|
|
40
|
+
return SEL_QUOTE + pad32(tokenIn) + pad32(tokenOut) + uintToHex(amountIn) + uintToHex(BigInt(fee)) + uintToHex(0n);
|
|
41
|
+
}
|
|
42
|
+
function decodeUint(hex, word = 0) {
|
|
43
|
+
const h = hex.toLowerCase().replace(/^0x/, "");
|
|
44
|
+
const w = h.slice(word * 64, word * 64 + 64);
|
|
45
|
+
return w ? BigInt("0x" + w) : 0n;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function ethCall(rpc, to, data, from = ADDR_ZERO) {
|
|
49
|
+
const res = await fetch(rpc, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "Content-Type": "application/json" },
|
|
52
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_call", params: [{ to, data, from }, "latest"] }),
|
|
53
|
+
signal: AbortSignal.timeout(15_000),
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok) throw new Error(`eth_call HTTP ${res.status}`);
|
|
56
|
+
const json = await res.json();
|
|
57
|
+
if (json.error) throw new Error(`QuoterV2 reverted: ${JSON.stringify(json.error)}`);
|
|
58
|
+
if (!json.result) throw new Error("eth_call returned no result");
|
|
59
|
+
return json.result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readSailEnv() {
|
|
63
|
+
const p = resolvePath(process.cwd(), ".sail", ".env.local");
|
|
64
|
+
const out = {};
|
|
65
|
+
if (existsSync(p)) {
|
|
66
|
+
for (const line of readFileSync(p, "utf8").split("\n")) {
|
|
67
|
+
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
|
|
68
|
+
if (m) out[m[1]] = m[2].replace(/^["']|["']$/g, "");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
function readSailConfig() {
|
|
74
|
+
const p = resolvePath(process.cwd(), ".sail", "config.json");
|
|
75
|
+
if (!existsSync(p)) return {};
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
78
|
+
} catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function resolveChain(chainFlag) {
|
|
83
|
+
if (chainFlag) {
|
|
84
|
+
const c = CHAINS[chainFlag.toLowerCase()];
|
|
85
|
+
if (c) return { name: chainFlag.toLowerCase(), ...c };
|
|
86
|
+
for (const [name, cfg] of Object.entries(CHAINS)) if (String(cfg.chainId) === String(chainFlag)) return { name, ...cfg };
|
|
87
|
+
throw new Error(`Unknown chain "${chainFlag}". Known: ${Object.keys(CHAINS).join(", ")}`);
|
|
88
|
+
}
|
|
89
|
+
const env = readSailEnv();
|
|
90
|
+
const cfg = readSailConfig();
|
|
91
|
+
const id = env.CHAIN_ID ?? cfg.chainId;
|
|
92
|
+
if (id) for (const [name, c] of Object.entries(CHAINS)) if (String(c.chainId) === String(id)) return { name, ...c };
|
|
93
|
+
throw new Error(`Could not resolve chain. Pass --chain <${Object.keys(CHAINS).join("|")}>.`);
|
|
94
|
+
}
|
|
95
|
+
function resolveRpc(chain, rpcFlag) {
|
|
96
|
+
if (rpcFlag) return rpcFlag;
|
|
97
|
+
const env = readSailEnv();
|
|
98
|
+
// Mirror resolve-token.mjs / packages/cli getRpcUrl(): named var, then chainId-keyed
|
|
99
|
+
// var (RPC_URL_8453 — written by the UI / used by multi-chain projects), then generic.
|
|
100
|
+
const nameVar = `${chain.name.toUpperCase().replace("-", "_")}_RPC_URL`;
|
|
101
|
+
const idVar = `RPC_URL_${chain.chainId}`;
|
|
102
|
+
return env[nameVar] ?? env[idVar] ?? env.RPC_URL ?? null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseArgs(argv) {
|
|
106
|
+
const out = {};
|
|
107
|
+
for (let i = 0; i < argv.length; i++) {
|
|
108
|
+
const a = argv[i];
|
|
109
|
+
if (a.startsWith("--")) {
|
|
110
|
+
const key = a.replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
111
|
+
out[key] = argv[++i];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function fmtUnits(valueBI, decimals) {
|
|
118
|
+
// bigint base units → human string, preserving precision
|
|
119
|
+
const neg = valueBI < 0n;
|
|
120
|
+
let s = (neg ? -valueBI : valueBI).toString().padStart(decimals + 1, "0");
|
|
121
|
+
const intPart = s.slice(0, s.length - decimals);
|
|
122
|
+
const fracPart = decimals > 0 ? s.slice(s.length - decimals) : "";
|
|
123
|
+
const trimmedFrac = fracPart.replace(/0+$/, "");
|
|
124
|
+
let out = trimmedFrac.length ? `${intPart}.${trimmedFrac}` : intPart;
|
|
125
|
+
if (decimals > 0 && !trimmedFrac) out = `${intPart}.0`;
|
|
126
|
+
return (neg ? "-" : "") + out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function errMsg(e) {
|
|
130
|
+
return e && typeof e.message === "string" ? e.message : String(e);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function main() {
|
|
134
|
+
const a = parseArgs(process.argv.slice(2));
|
|
135
|
+
if (process.argv.length <= 2 || a.help) {
|
|
136
|
+
process.stderr.write(
|
|
137
|
+
"Usage: quote-swap.mjs --token-in <addr> --token-out <addr> --amount <baseUnits>\n" +
|
|
138
|
+
" --decimals-in <n> --decimals-out <n> --fee <tier> [--slippage-bps <n>] [--chain <>] [--rpc URL]\n",
|
|
139
|
+
);
|
|
140
|
+
process.exit(process.argv.length <= 2 ? 1 : 0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const need = ["tokenIn", "tokenOut", "amount", "decimalsIn", "decimalsOut", "fee"];
|
|
144
|
+
for (const k of need) if (a[k] === undefined) throw new Error(`Missing --${k.replace(/([A-Z])/g, "-$1").toLowerCase()}`);
|
|
145
|
+
|
|
146
|
+
const chain = resolveChain(a.chain);
|
|
147
|
+
const rpc = resolveRpc(chain, a.rpc);
|
|
148
|
+
if (!rpc) throw new Error(`No RPC for ${chain.name}. Pass --rpc or set RPC_URL in .sail/.env.local.`);
|
|
149
|
+
|
|
150
|
+
const tokenIn = a.tokenIn;
|
|
151
|
+
const tokenOut = a.tokenOut;
|
|
152
|
+
const amountIn = BigInt(a.amount);
|
|
153
|
+
const fee = Number(a.fee);
|
|
154
|
+
const decimalsIn = Number(a.decimalsIn);
|
|
155
|
+
const decimalsOut = Number(a.decimalsOut);
|
|
156
|
+
const slippageBps = a.slippageBps !== undefined ? Number(a.slippageBps) : 100; // default 1%
|
|
157
|
+
if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps > 10_000) {
|
|
158
|
+
throw new Error(`--slippage-bps must be an integer in [0, 10000], got "${a.slippageBps}".`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const data = encodeQuoteCall(tokenIn, tokenOut, amountIn, fee);
|
|
162
|
+
const ret = await ethCall(rpc, chain.quoterV2, data);
|
|
163
|
+
const amountOut = decodeUint(ret, 0); // word 0
|
|
164
|
+
const gasEstimate = decodeUint(ret, 3); // word 3
|
|
165
|
+
|
|
166
|
+
if (amountOut === 0n) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`QuoterV2 returned 0 for ${amountIn} (tokenIn) → tokenOut at fee ${fee}. The pool is empty or does not exist at this tier.`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const amountOutMinimum = (amountOut * BigInt(10_000 - slippageBps)) / 10_000n;
|
|
173
|
+
// price = (amountOut / 10^decOut) / (amountIn / 10^decIn) → units of tokenOut per tokenIn
|
|
174
|
+
const humanIn = fmtUnits(amountIn, decimalsIn);
|
|
175
|
+
const humanOut = fmtUnits(amountOut, decimalsOut);
|
|
176
|
+
const humanMin = fmtUnits(amountOutMinimum, decimalsOut);
|
|
177
|
+
|
|
178
|
+
const out = {
|
|
179
|
+
tokenIn,
|
|
180
|
+
tokenOut,
|
|
181
|
+
decimalsIn,
|
|
182
|
+
decimalsOut,
|
|
183
|
+
amountIn: amountIn.toString(),
|
|
184
|
+
amountOut: amountOut.toString(),
|
|
185
|
+
amountOutMinimum: amountOutMinimum.toString(),
|
|
186
|
+
slippageBps,
|
|
187
|
+
fee,
|
|
188
|
+
chain: chain.name,
|
|
189
|
+
chainId: chain.chainId,
|
|
190
|
+
gasEstimate: gasEstimate.toString(),
|
|
191
|
+
human: {
|
|
192
|
+
amountIn: humanIn,
|
|
193
|
+
amountOut: humanOut,
|
|
194
|
+
amountOutMinimum: humanMin,
|
|
195
|
+
price: `${humanOut} out per ${humanIn} in`,
|
|
196
|
+
},
|
|
197
|
+
note: "amountOutMinimum is the slippage floor — embed it in the swap calldata the agent dispatches.",
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
201
|
+
process.stderr.write(
|
|
202
|
+
`\nQuote: ${humanIn} in → ${humanOut} out (fee ${fee})\n` +
|
|
203
|
+
` amountOutMinimum (${slippageBps / 100}% slip): ${humanMin}\n` +
|
|
204
|
+
` gas estimate: ${gasEstimate}\n`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
main().catch((err) => {
|
|
209
|
+
process.stderr.write(`\nquote-swap failed: ${errMsg(err)}\n`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
});
|