Install
$ agentstack add skill-injectivelabs-agent-skills-injective-funding ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Injective Mass Funding Skill
Overview
Fund many Injective wallets efficiently. Batch bank transfers, deposit to exchange subaccounts, and verify balances. Based on an Injective RFQ test framework.
Reference Code
- Seed-based funding:
scripts/fund_subaccounts.py - Two-step USDT funding:
scripts/fund_demo_subaccounts.py - Generate + fund 100 wallets:
scripts/setup_wallets.py - Top up takers:
scripts/top_up_demo.py - Bank to subaccount deposit:
scripts/move_to_sub.py - Balance checks:
scripts/check_balances.py,scripts/check_all_balances.py,scripts/check_funding.py
Batch Bank Transfers
200 Transfers in One Transaction
From setup_wallets.py — batch up to 200 MsgSend in a single tx:
from pyinjective.composer import Composer
from pyinjective.transaction import Transaction
composer = Composer(network=network.string())
msgs = []
for wallet in wallets:
# Send INJ
msgs.append(composer.msg_send(
sender=funder_address,
receiver=wallet.inj_address,
amount=inj_amount, # in wei (1e18)
denom="inj"
))
# Send USDT
msgs.append(composer.msg_send(
sender=funder_address,
receiver=wallet.inj_address,
amount=usdt_amount, # in 1e6
denom=usdt_denom
))
# Build and broadcast single tx with all msgs
tx = Transaction(msgs=msgs, ...)
Quote-asset denoms by network
| Network | Asset | Denom | Decimals | |---------|-------|-------|----------| | Mainnet | USDT | peggy0xdAC17F958D2ee523a2206206994597C13D831ec7 | 6 | | Mainnet | USDC | peggy0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 | 6 | | Testnet | USDT | peggy0x87aB3B4C8661e07D6372361211B96ed4Dc36B1B5 | 6 | | Testnet | USDC | erc20:0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d | 6 | | Devnet | varies per config | — | 6 |
Which asset a given RFQ/exchange market uses depends on its quote_denom. Query the market via LCD (/injective/exchange/v2/derivative/markets/{id}) and look at market.quote_denom — never hardcode.
INJ Amounts
INJ uses 18 decimals (like ETH wei):
- 1 INJ =
1000000000000000000(1e18) - 0.1 INJ =
100000000000000000(1e17)
Subaccount Deposits
After funding bank accounts, move to exchange subaccounts for trading:
from pyinjective.core.broadcaster import MsgBroadcasterWithPk
msg = composer.msg_subaccount_deposit(
sender=address,
subaccount_id=get_subaccount_id(address, nonce=0),
amount=Decimal(str(amount)),
denom=denom
)
broadcaster = MsgBroadcasterWithPk.new_using_simulation(
network=network,
private_key=private_key
)
await broadcaster.broadcast([msg])
Subaccount ID Calculation
def get_subaccount_id(inj_address: str, nonce: int = 0) -> str:
"""Generate subaccount ID: eth_address + nonce (padded to 24 hex chars)"""
eth_addr = inj_to_eth_address(inj_address)
return eth_addr.lower() + format(nonce, '024x')
Two-Step USDT Funding (Testnet Pattern)
- Parent sends USDT to child via bank
MsgSend - Child deposits USDT from bank to exchange subaccount
This is needed because on testnet, the public faucet only gives INJ, not USDT/USDC. You need to pre-fund a parent wallet with the quote asset.
Public Faucet Server (Accept-Either-Address Pattern)
Pattern for a browser-facing faucet that takes a caller-supplied address and sends INJ + quote-asset in a single MsgSend. Proven working against testnet INJ/USDC PERP (2026-04-20; tx EDE9788844E1780DC72ABBC67346550A85E842A57BF9A451FDDD130125B98163).
Principles
- Accept both
inj1…and0x…— same secp256k1 key, two encodings. MetaMask/bridge users only have the0xform; Cosmos-native users pasteinj1. Forcing one side creates a support tax. See injective-wallet-ops for the conversion helpers. - Canonicalize to
inj1at the boundary.MsgSendwants bech32 receivers; more importantly, rate limits + dedup keys must be keyed on the canonical form. Otherwise a caller can flip encodings to dodge a cooldown. - Per-request hard caps (e.g. 500 USDC + 2 INJ) enforced server-side, not trusted from the request body.
- Per-address cooldown in a file-backed map (
{ addr: { lastRequestAt, lastTxHash } }). SQLite is overkill for testnet volumes. - Status endpoint that returns hot-wallet balance + remaining budget. Useful for dashboards and for
npm run dev-time debugging; optional to expose publicly. - Fresh hot wallet per deployment, not reused across envs. Fund it with just enough for ~100 requests so a leak is a bounded loss. Top up on demand.
Reference implementation (Node + @injectivelabs/sdk-ts)
import 'dotenv/config';
import express from 'express';
import {
PrivateKey, MsgSend, MsgBroadcasterWithPk,
getInjectiveAddress, getEthereumAddress,
} from '@injectivelabs/sdk-ts';
import { Network } from '@injectivelabs/networks';
const FAUCET_PK = process.env.FAUCET_PRIVATE_KEY;
const USDC_DENOM = 'erc20:0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d'; // testnet
const USDC_SUB = (500n * 10n**6n ).toString(); // 500 USDC
const INJ_SUB = (2n * 10n**18n).toString(); // 2 INJ
const COOLDOWN_MS = 24 * 60 * 60 * 1000;
const priv = PrivateKey.fromHex('0x' + FAUCET_PK.replace(/^0x/, ''));
const FAUCET_ADDR = priv.toBech32();
const broadcaster = new MsgBroadcasterWithPk({ privateKey: FAUCET_PK, network: Network.Testnet });
// 1. Accept either form, canonicalize to inj1 for downstream use
const INJ_BECH32 = /^inj1[02-9ac-hj-np-z]{38}$/;
const ETH_HEX = /^0x[0-9a-fA-F]{40}$/;
function normalize(raw) {
const s = (raw || '').trim();
if (INJ_BECH32.test(s)) return { inj: s, eth: getEthereumAddress(s).toLowerCase() };
if (ETH_HEX.test(s)) return { inj: getInjectiveAddress(s), eth: s.toLowerCase() };
throw new Error('malformed address — expected inj1… (43 chars) or 0x… (42 chars)');
}
// 2. File-backed rate limit, keyed on the inj form
const limits = new Map(); // load from disk on boot, persist on each record
// 3. The send itself — a single MsgSend with both denoms in one tx
async function sendFunds(recipientInj) {
const msg = MsgSend.fromJSON({
srcInjectiveAddress: FAUCET_ADDR,
dstInjectiveAddress: recipientInj,
amount: [{ denom: USDC_DENOM, amount: USDC_SUB }, { denom: 'inj', amount: INJ_SUB }],
});
const tx = await broadcaster.broadcast({ msgs: [msg] });
return tx.txHash;
}
const app = express();
app.use(express.json());
app.post('/api/faucet', async (req, res) => {
let parsed;
try { parsed = normalize(req.body?.address); }
catch (e) { return res.status(400).json({ error: e.message }); }
const prior = limits.get(parsed.inj);
if (prior && Date.now() - prior.lastRequestAt =1.12.0`
- `httpx>=0.27`
- Python >= 3.11
**Node (public faucet server + browser-origin address inputs):**
- `@injectivelabs/sdk-ts` — `PrivateKey`, `MsgSend`, `MsgBroadcasterWithPk`, `getInjectiveAddress`, `getEthereumAddress`
- `@injectivelabs/networks` — `Network.Testnet` / `Network.Mainnet`
- `express`, `dotenv`
- Node >= 20
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [InjectiveLabs](https://github.com/InjectiveLabs)
- **Source:** [InjectiveLabs/agent-skills](https://github.com/InjectiveLabs/agent-skills)
- **License:** Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.