@31third/sdk
v0.1.0-alpha.4
Published
31Third SDK for Safe automation deployment and policy-guarded execution
Readme
@31third/sdk
SDK for deploying the StrategyExecutor framework on Safe and executing policy-guarded rebalancings. It helps you create the Safe proposal, attach on-chain policies, and run an execution wallet (AI agent or automation) that can trade only when policies pass.
Table of contents:
- Concept
- Install
- Quick start (single-wallet executor)
- Chainlink Automation mode
- Deploy StrategyExecutor + Policies (Safe proposal)
- Calculate and execute rebalancing
- Rebalancing payload parameters (SDK-relevant)
- Chain overrides
- Policy reference
- AI integration guide (structured)
- Current status
Concept
Important: Safe signers that approve the module deployment must be different from the StrategyExecutor execution wallet. The execution wallet is hot and should never be used to propose or sign the Safe deployment.
Create a Safe, deploy a StrategyExecutor module, and add policies that guard on-chain execution. This lets you run an AI agent (or any other trading automation) that can execute trades only if the policies pass. The Safe still owns the assets; the StrategyExecutor enforces the rules.
Setup overview:
- Create a Safe and fund it with assets.
- Choose an execution wallet (hot wallet) that will be the scheduler and registry in single-wallet mode.
- Have Safe signers propose and approve the deployment of the StrategyExecutor module and policies.
- The execution wallet (AI agent or automation) calculates rebalances and calls
executeTradeNow.
Two execution modes:
- Single-wallet executor (recommended for most use cases): one wallet is both scheduler and registry. The AI agent signs directly and calls
executeTradeNow. - Chainlink Automation executor: your wallet is the scheduler, and the Chainlink registry triggers execution. Use a registry address for the chain and keep scheduler/registry distinct.
Quick links:
- Safe app: https://app.safe.global/
- Chainlink Automation supported networks: https://docs.chain.link/chainlink-automation/overview/supported-networks
Security notes:
- Use a dedicated hot wallet for execution with limited funds.
- Keep policies strict (asset universe, slippage, allocation) before enabling autonomous execution.
- Treat the executor wallet as production infrastructure; rotate keys and monitor activity.
Registry addresses (Chainlink Automation v2.1+)
- Ethereum mainnet (1):
0x6593c7De001fC8542bB1703532EE1E5aA0D458fD - Polygon mainnet (137):
0x08a8eea76D2395807Ce7D1FC942382515469cCA1 - Base mainnet (8453):
0xf4bAb6A129164aBa9B113cB96BA4266dF49f8743 - Arbitrum One (42161):
0x37D9dC70bfcd8BC77Ec2858836B923c560E891D1
Install
npm install @31third/sdkDeployment note: the module deployment proposal must be created by a Safe signer, not the execution wallet.
Quick start (single-wallet executor)
Steps:
- Create a Safe and fund it with assets on https://app.safe.global/.
- Deploy StrategyExecutor with policies (single-wallet mode).
- Calculate a rebalancing.
- Execute the rebalancing.
import { proposeDeployModuleBatchTx } from '@31third/sdk';
import { JsonRpcProvider, Wallet } from 'ethers';
const provider = new JsonRpcProvider(process.env.RPC_URL!);
const safeSigner = new Wallet(process.env.SAFE_SIGNER_PK!, provider);
const safe = process.env.SAFE_ADDRESS!;
const scheduler = process.env.EXECUTOR_ADDRESS!;
const registry = process.env.EXECUTOR_ADDRESS!;
const feedRegistry = process.env.FEED_REGISTRY!;
await proposeDeployModuleBatchTx({
automationId: 'uuid-or-id',
safeAddress: safe,
signer: safeSigner, // Safe signer, not the executor
chainId: 8453,
scheduler, // scheduler == registry for single-wallet mode
registry,
batchTrade: '0xBatchTrade',
// Hint: include only the policies you need.
policyConfig: {
cooldown: 3600,
assetUniverse: {
assetUniverseTokens: ['0xToken1', '0xToken2'],
},
slippage: {
feedRegistry,
maxSlippageBps: 50,
},
staticAllocation: {
feedRegistry,
targetTokens: ['0xToken1', '0xToken2'],
targetBps: [5000, 5000],
triggerThresholdBps: 50,
toleranceThresholdBps: 50,
},
},
});import { calculateRebalancing } from '@31third/sdk';
const rebalancing = await calculateRebalancing({
apiBaseUrl: 'https://api.31third.com/1.3',
apiKey: process.env.API_KEY!,
chainId: 8453,
payload: {
wallet: process.env.SAFE_ADDRESS,
signer: process.env.SAFE_ADDRESS,
baseEntries: [],
targetEntries: [],
maxSlippage: 0.01,
maxPriceImpact: 0.05,
minTradeValue: 0.01,
skipBalanceValidation: false,
},
});import { executeRebalancing } from '@31third/sdk';
import { JsonRpcProvider, Wallet } from 'ethers';
const executor = new Wallet(process.env.EXECUTOR_PK!, new JsonRpcProvider(process.env.RPC_URL!));
const tx = await executeRebalancing({
signer: executor,
strategyExecutor: process.env.STRATEGY_EXECUTOR!,
rebalancing,
});
await tx.wait();Rebalancing payload parameters (SDK-relevant)
These are the fields you can set in calculateRebalancing({ payload }):
wallet: Safe address the rebalancing is calculated for.signer: address that will sign/execute on-chain.simulationTxOrigin: optional override for Tenderly simulation (if needed).baseEntries: list of{ tokenAddress, amount }for the assets to sell (amount in wei).targetEntries: list of{ tokenAddress, allocation }(allocation as fraction, e.g.0.2= 20%).maxDeviationFromTarget: max deviation from target allocation (default 0.005).maxSlippage: max slippage for calculated trades (default 0.005).maxPriceImpact: max price impact for the basket trade (default 0.01).minTradeValue: minimum trade value in USD (default 1).batchTrade: enable batch trade (default true).revertOnError: revert whole batch if one trade fails (default true).skipBalanceValidation: skip balance checks (default false).failOnMissingPricePair: fail if any required price pair is missing (default true).async: calculate asynchronously (default false).
Token address note: use ETH or 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee for native ETH.
Chain overrides
You can override chain constants by passing chainOverrides to deployment helpers:
import { proposeDeployModuleBatchTx } from '@31third/sdk';
await proposeDeployModuleBatchTx({
/* ... */
chainOverrides: {
safeTxServiceUrls: {
8453: 'https://safe-transaction-base.safe.global',
},
multisendAddresses: {
8453: '0x38869bf66a61cF6bDB996A6aE40D5853Fd43B526',
},
create2FactoryAddresses: {
8453: '0xce0042B868300000d44A59004Da54A005ffdcf9f',
},
safeAppPrefixes: {
8453: 'base',
},
},
});Fields:
safeTxServiceUrls: Safe Transaction Service API per chain id.multisendAddresses: MultiSend contract address per chain id.create2FactoryAddresses: CREATE2 factory per chain id.safeAppPrefixes: Safe app URL prefix per chain id.
Policy reference
This SDK can deploy these policies and attach them to the StrategyExecutor. All policies run on-chain before execution and can veto trades.
AssetUniversePolicy
Purpose: restrict trades to a fixed list of allowed tokens.
How it works:
- On every trade, checks that
trade.fromandtrade.toare in the allowed token set. - Any token not explicitly allowed fails the policy.
Owner controls:
allowToken(token)adds a token to the universe.disallowToken(token)removes a token from the universe.
Failure modes (custom errors):
AssetUniverseFromTokenNotAllowed(token)AssetUniverseToTokenNotAllowed(token)
Use cases:
- Prevent AI or external executors from trading into unknown assets.
- Enforce allowlists per portfolio or strategy.
StaticAllocationPolicy
Purpose: enforce a target portfolio allocation (in bps) and only allow rebalances when a deviation threshold is met.
Inputs:
targetTokensandtargetBps(must sum to 10_000).assetUniverse(optional, if provided it defines the full supported token set).feedRegistryfor price feeds.triggerThresholdBps: minimum deviation from target that triggers a rebalance.toleranceThresholdBps: maximum deviation allowed after executing the proposed trades.
How it works:
- Loads current balances for all supported tokens.
- Computes current USD weights via feed registry.
- If no token deviates by at least
triggerThresholdBps, it fails withStaticAllocationNoRebalanceTrigger. - Simulates the proposed trades using
minToReceiveBeforeFeesand updates balances. - Computes predicted USD weights. If any token is outside
toleranceThresholdBps, it fails.
Failure modes (custom errors):
StaticAllocationFeedMissing(token)StaticAllocationZeroPortfolioValue()StaticAllocationNoRebalanceTrigger()StaticAllocationTokenNotSupported(token)StaticAllocationInsufficientBalance(token, required, available)StaticAllocationZeroPredictedValue()StaticAllocationOutsideTolerance()
Notes:
- Requires all tokens to have price feeds in the registry.
targetTokensmust be in the asset universe whenassetUniverseis provided.- Use
assetUniverseto include on-ramp tokens (e.g., USDC/USDT) even if the target allocation is only WBTC/WETH; the universe defines which tokens the strategy can trade through.
SlippagePolicy
Purpose: ensure minToReceiveBeforeFees isn’t below a maximum slippage bound.
Inputs:
feedRegistryfor price feeds.maxSlippageBps(<= 10_000).
How it works:
- For each trade, gets USD price feeds for
fromandto. - Computes expected output amount and enforces
minToReceiveBeforeFees >= expected * (1 - maxSlippageBps/10_000).
Failure modes (custom errors):
SlippageFeedMissing(tradeIndex, token)SlippageMinToReceiveTooLow(tradeIndex, minToReceive, minAllowed)
Notes:
- Requires feeds for both
fromandtotokens. - Uses token decimals and feed decimals to normalize expected output.
AI integration guide (structured)
Use this when implementing an AI agent that proposes and executes trades:
Inputs:
SAFE_ADDRESS: Safe that holds assets.STRATEGY_EXECUTOR: Deployed module address.RPC_URL: Chain RPC.API_KEY: 31Third API key for rebalancing.CHAIN_ID: 1 / 137 / 8453 / 42161.EXECUTOR_PK: Private key of the executor wallet (scheduler==registry).
Steps:
- Fetch current Safe token balances and use those amounts for
baseEntriesincalculateRebalancing. It is not necessary to include all balances; include only the tokens/amounts you want to rebalance. All selected tokens must be in the defined asset universe. - Call
calculateRebalancingwithrebalancing/walletpayload. - Decode
txDatainto trades/config. - Call
checkPoliciesVerboseon StrategyExecutor. - If ok, call
executeTradeNowwith approvals and encoded trade data. - Record tx hash and any policy failure reasons for auditing.
Expected outputs:
- Rebalancing response (txData + requiredAllowances).
- Execution tx hash (or a policy failure reason).
Current status
- Only Base is supported right now.
- Default Base feed registry:
0x1DB380699aEBCEdf252C8a91E20867D8A09c1dcd - Supported Base tokens:
- WETH
0x4200000000000000000000000000000000000006 - WBTC
0x0555E30da8f98308EdB960aa94C0Db47230d2B9c - USDC
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 - EURC
0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42 - cbBTC
0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf - cbETH
0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22 - AAVE
0x54330d28ca3357F294334BDC454a032e7f353416 - LINK
0x88Fb150BDc53A65fe94Dea0c9BA0a6dAf8C6e196 - USDe
0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34 - sUSDe
0x2113938A47a0F084bB494c289c8A5c0c98D8Ca2d - MORPHO
0xBaA644bCfe0E546D67f58E5E8EC529618B8B8350
- WETH
