skills/injective-trading-autosign/SKILL.md
Set up AuthZ delegation on Injective for session-based auto-trading. Grants a scoped, time-limited permission to an ephemeral key so the AI can place and close perpetual trades without a wallet popup or password prompt for every order. Use authz_grant to enable, authz_revoke to disable. Requires the Injective MCP server to be connected.
npx skillsauth add injectivelabs/agent-skills injective-trading-autosignInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
AuthZ delegation lets a user grant a scoped, on-chain permission to a secondary key (the "grantee") to execute specific message types on their behalf. This enables session-based trading without password prompts per trade.
Security model: The grant is scoped to specific Cosmos message types (trading only - no withdrawals or transfers). It expires automatically. The user can revoke at any time.
Sample prompts: ./references/sample-prompts.md
Only grant these types - they cover all perpetual trading operations:
| Message Type | What it allows |
|---|---|
| MsgCreateDerivativeMarketOrder | Open/close positions via market order |
| MsgCreateDerivativeLimitOrder | Place limit orders |
| MsgCancelDerivativeOrder | Cancel limit orders |
| MsgBatchUpdateOrders | Batch order operations |
| MsgIncreasePositionMargin | Add margin to existing position |
Never grant MsgSend, MsgWithdraw, governance messages, or any transfer-related types.
When implementing AutoSign in a browser frontend (e.g. autosign.ts with enableAutoSign):
The evmChainId stored in the AutoSign state must be the actual wallet chain at the moment the grant tx is signed, NOT a hardcoded value:
// Correct — read from wallet
const evmChainId = parseInt(await window.ethereum.request({ method: 'eth_chainId' }), 16);
// Wrong — hardcoding bypasses wallet chain enforcement
const evmChainId = 1776;
Wallet connection: Use eth_requestAccounts (respects default wallet provider). Do NOT use wallet_requestPermissions — it forces MetaMask even when Rabby is the default.
// Good — respects Rabby, MetaMask, or whatever is default
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
// Bad — forces MetaMask account picker, ignores Rabby
await window.ethereum.request({ method: 'wallet_requestPermissions', params: [{ eth_accounts: {} }] });
Disconnect: Use wallet_revokePermissions to clear cached accounts so next connect shows the wallet picker:
await window.ethereum?.request({ method: 'wallet_revokePermissions', params: [{ eth_accounts: {} }] });
Rabby throws different error codes than MetaMask on wallet_switchEthereumChain. Handle gracefully:
try {
await window.ethereum.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: '0x6f0' }] });
} catch {
try {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: '0x6f0',
chainName: 'Injective',
nativeCurrency: { name: 'Injective', symbol: 'INJ', decimals: 18 },
rpcUrls: ['https://sentry.evm-rpc.injective.network/'],
blockExplorerUrls: ['https://blockscout.injective.network'],
}],
});
} catch { /* Rabby may reject but still be on the right chain */ }
}
// Always re-check — don't trust the switch/add result
const recheckChain = await window.ethereum.request({ method: 'eth_chainId' });
if (parseInt(recheckChain, 16) !== 1776) throw new Error('Switch to Injective (chain ID 1776)');
IMPORTANT: inEVM is DEPRECATED. Use sentry.evm-rpc.injective.network/ (NOT mainnet.rpc.inevm.com). Block explorer: blockscout.injective.network.
Fresh accounts have no on-chain public key. Using a zero-byte placeholder causes the chain to panic with invalid secp256k1 public key. Recover the real pubkey via personal_sign:
import { ethers } from 'ethers'
async function recoverPubKeyFromWallet(ethAddress: string): Promise<string> {
const msg = `Injective account verification: ${ethAddress}`
const sig = await window.ethereum.request({ method: 'personal_sign', params: [msg, ethAddress] });
const msgHash = ethers.hashMessage(msg)
const uncompressed = ethers.SigningKey.recoverPublicKey(msgHash, sig)
const compressed = ethers.SigningKey.computePublicKey(uncompressed, true)
return btoa(String.fromCharCode(...ethers.getBytes(compressed)))
}
// In createTransaction:
const pubKey = onChainPubKey || await recoverPubKeyFromWallet(ethAddress)
ChainRestTendermintApi.fetchLatestBlock() gets cached by browsers. Use raw fetch with cache-bust:
const blockRes = await fetch(
`${endpoints.rest}/cosmos/base/tendermint/v1beta1/blocks/latest?_=${Date.now()}`,
{ cache: 'no-store' }
).then(r => r.json());
const timeoutHeight = parseInt(blockRes.block.header.height, 10) + 200; // +200 blocks, not +20
+20 blocks is too tight — with faucet delays, chain switching, and wallet popups, use +200 minimum (~3 minutes of runway).
authz_grant
granterAddress: inj1... ← your main wallet
password: **** ← keystore password (one-time, for signing the grant tx)
granteeAddress: inj1... ← ephemeral/session key to grant permission to
msgTypes: ← list of allowed message types
- MsgCreateDerivativeMarketOrder
- MsgCreateDerivativeLimitOrder
- MsgCancelDerivativeOrder
- MsgBatchUpdateOrders
- MsgIncreasePositionMargin
expirySeconds: 86400 ← optional, default 86400 (24h). Max recommended: 259200 (72h)
After this one transaction, the grantee can trade on behalf of the granter for the duration.
authz_revoke
granterAddress: inj1...
password: ****
granteeAddress: inj1...
msgTypes:
- MsgCreateDerivativeMarketOrder
- MsgCreateDerivativeLimitOrder
- MsgCancelDerivativeOrder
- MsgBatchUpdateOrders
- MsgIncreasePositionMargin
Revoke immediately cancels all permissions for the specified message types. Partial revocation (removing specific msg types) is supported.
# Check existing grants on-chain (via Injective Indexer or explorer)
# injective.network → account → authz tab
# Grant (one-time per session)
authz_grant(granterAddress, password, granteeAddress, msgTypes, expirySeconds: 86400)
# Trade freely during session - no password needed if using grantee key
# ...
# Revoke when done
authz_revoke(granterAddress, password, granteeAddress, msgTypes)
MetaMask must be on either of these networks when granting AutoSign:
The same evmChainId used during the grant transaction MUST be used for all subsequent broadcastAutoSign calls.
injective-mcp-serversIf these skills are not available, selectively run the following commands to install them:
npx skills add InjectiveLabs/agent-skills --skill injective-mcp-servers
@injectivelabs/sdk-ts: 1.17.8development
Detect breaking changes in Injective core between two tagged releases. For use in developer documentation and release notes.
development
Integrate Injective RFQ taker flows into browser apps and operational quote monitors. Use this skill when building, reviewing, or debugging RFQ gateway autosign settlement, Web3Gateway AuthZ setup, manual TakerStream quote collection, RFQ open/close flows, conditional TP/SL intents, quote uptime probes, market-readiness checks, or mainnet RFQ frontend integrations. Covers mainnet parameters, canonical decimals, quote windows, signer slots, market discovery, quote-hit diagnostics, and production RFQ gotchas.
tools
Enables management of Linear issues, teams, projects, comments, or configuration via the `linear` CLI
tools
Mass create, derive, and manage Injective wallets. Generate wallets from mnemonics (BIP-44 HD derivation), create random wallets, convert between ETH/INJ addresses, and batch fund wallets with INJ or USDT. Supports bulk wallet generation and batch funding.