Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/connector-ui/public/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ polygon-agent wallet remove [--name <n>]

### Operations
```bash
polygon-agent balances [--wallet <n>] [--chain <chain>]
polygon-agent balances [--wallet <n>] [--chain <chain>] [--chains <csv>]
polygon-agent send --to <addr> --amount <num> [--symbol <SYM>] [--token <addr>] [--decimals <n>] [--broadcast]
polygon-agent send-native --to <addr> --amount <num> [--broadcast] [--direct]
polygon-agent send-token --symbol <SYM> --to <addr> --amount <num> [--token <addr>] [--decimals <n>] [--broadcast]
Expand Down Expand Up @@ -127,6 +127,7 @@ polygon-agent agent feedback --agent-id <id> --value <score> [--tag1 <t>] [--tag

- **Dry-run by default** — all write commands require `--broadcast` to execute
- **Smart defaults** — `--wallet main`, `--chain polygon`, auto-wait on `wallet create`
- **`balances --chains`** — comma-separated chains (max 20); two or more return JSON with `multiChain: true` and a `chains` array (same wallet address on each)
- **Fee preference** — auto-selects USDC over native POL when both available
- **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address.
- **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract <depositAddress>`
Expand Down
3 changes: 2 additions & 1 deletion packages/connector-ui/public/skills/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ polygon-agent wallet remove [--name <n>]

### Operations
```bash
polygon-agent balances [--wallet <n>] [--chain <chain>]
polygon-agent balances [--wallet <n>] [--chain <chain>] [--chains <csv>]
polygon-agent send --to <addr> --amount <num> [--symbol <SYM>] [--token <addr>] [--decimals <n>] [--broadcast]
polygon-agent send-native --to <addr> --amount <num> [--broadcast] [--direct]
polygon-agent send-token --symbol <SYM> --to <addr> --amount <num> [--token <addr>] [--decimals <n>] [--broadcast]
Expand Down Expand Up @@ -127,6 +127,7 @@ polygon-agent agent feedback --agent-id <id> --value <score> [--tag1 <t>] [--tag

- **Dry-run by default** — all write commands require `--broadcast` to execute
- **Smart defaults** — `--wallet main`, `--chain polygon`, auto-wait on `wallet create`
- **`balances --chains`** — comma-separated chains (max 20); two or more return JSON with `multiChain: true` and a `chains` array (same wallet address on each)
- **Fee preference** — auto-selects USDC over native POL when both available
- **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address.
- **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract <depositAddress>`
Expand Down
5 changes: 4 additions & 1 deletion packages/polygon-agent-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ polygon-agent fund # Open funding widget
### Token Operations

```bash
polygon-agent balances # Check all balances
polygon-agent balances # Balances on session default chain
polygon-agent balances --chain arbitrum # Single chain override
polygon-agent balances --chains polygon,base,arbitrum # Same wallet, multiple chains (JSON)
polygon-agent send --to 0x... --amount 1.0 # Send POL (dry-run)
polygon-agent send --symbol USDC --to 0x... --amount 10 --broadcast
polygon-agent swap --from USDC --to USDT --amount 5 --broadcast
Expand All @@ -194,6 +196,7 @@ polygon-agent agent reviews --agent-id <id>
| ------------- | ---------------------- | -------------------- |
| Wallet name | `main` | `--name <name>` |
| Chain | `polygon` | `--chain <name\|id>` |
| Multi-chain balances | — | `--chains <csv>` (comma-separated, max 20; overrides `--chain`) |
| Wallet create | Auto-wait for approval | `--no-wait` |
| Broadcast | Dry-run (preview) | `--broadcast` |

Expand Down
3 changes: 2 additions & 1 deletion packages/polygon-agent-cli/skills/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ polygon-agent wallet remove [--name <n>]

### Operations
```bash
polygon-agent balances [--wallet <n>] [--chain <chain>]
polygon-agent balances [--wallet <n>] [--chain <chain>] [--chains <csv>]
polygon-agent send --to <addr> --amount <num> [--symbol <SYM>] [--token <addr>] [--decimals <n>] [--broadcast]
polygon-agent send-native --to <addr> --amount <num> [--broadcast] [--direct]
polygon-agent send-token --symbol <SYM> --to <addr> --amount <num> [--token <addr>] [--decimals <n>] [--broadcast]
Expand Down Expand Up @@ -127,6 +127,7 @@ polygon-agent agent feedback --agent-id <id> --value <score> [--tag1 <t>] [--tag

- **Dry-run by default** — all write commands require `--broadcast` to execute
- **Smart defaults** — `--wallet main`, `--chain polygon`, auto-wait on `wallet create`
- **`balances --chains`** — comma-separated chains (max 20); two or more return JSON with `multiChain: true` and a `chains` array (same wallet address on each)
- **Fee preference** — auto-selects USDC over native POL when both available
- **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address.
- **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract <depositAddress>`
Expand Down
225 changes: 179 additions & 46 deletions packages/polygon-agent-cli/src/commands/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,114 @@ async function getTokenConfig({

const bigintReplacer = (_k: string, v: unknown) => (typeof v === 'bigint' ? v.toString() : v);

const BALANCES_MAX_CHAINS = 20;

function parseCommaChainList(chainsArg: string | undefined): string[] {
if (!chainsArg || typeof chainsArg !== 'string') return [];
return chainsArg
.split(',')
.map((s) => s.trim())
.filter(Boolean);
}

type BalanceRowJson =
| { type: 'native'; symbol: string; balance: string }
| {
type: 'erc20';
symbol: string;
name?: string;
contractAddress: string;
balance: string;
};

async function fetchBalancesRowsForChain(
walletAddress: string,
chainSpec: string,
indexerKey: string
): Promise<{ chainId: number; chain: string; balances: BalanceRowJson[] }> {
const network = resolveNetwork(chainSpec);
const nativeDecimals = network.nativeToken?.decimals ?? 18;
const nativeSymbol = network.nativeToken?.symbol || 'POL';

const { SequenceIndexer } = await import('@0xsequence/indexer');
const indexerUrl = getChainIndexerUrl(network.chainId);
const indexer = new SequenceIndexer(indexerUrl, indexerKey);

const [nativeRes, tokenRes] = await Promise.all([
indexer.getNativeTokenBalance({
accountAddress: walletAddress
}),
indexer.getTokenBalances({
accountAddress: walletAddress,
includeMetadata: true
})
]);

const nativeWei = nativeRes?.balance?.balance || '0';
const native: BalanceRowJson[] = [
{
type: 'native',
symbol: nativeSymbol,
balance: formatUnits(BigInt(nativeWei), nativeDecimals)
}
];

const erc20: BalanceRowJson[] = (tokenRes?.balances || []).map(
(b: {
contractInfo?: { symbol?: string; name?: string; decimals?: number };
contractAddress: string;
balance?: string;
}) => ({
type: 'erc20' as const,
symbol: b.contractInfo?.symbol || 'ERC20',
name: b.contractInfo?.name || undefined,
contractAddress: b.contractAddress,
balance: formatUnits(b.balance || '0', b.contractInfo?.decimals ?? 18)
})
);

return {
chainId: network.chainId,
chain: network.name,
balances: [...native, ...erc20]
};
}

// --- balances ---
export const balancesCommand: CommandModule = {
command: 'balances',
describe: 'Check token balances',
builder: (yargs) => withWalletAndChain(yargs),
builder: (yargs) =>
withWalletAndChain(yargs).option('chains', {
type: 'string',
describe:
'Comma-separated chain names or IDs (e.g. polygon,base,arbitrum). When set, overrides --chain. Two or more chains return multi-chain JSON (TTY included).'
}),
handler: async (argv) => {
const walletName = argv.wallet as string;
const chainListRaw = parseCommaChainList(argv.chains as string | undefined);
if (chainListRaw.length > BALANCES_MAX_CHAINS) {
console.error(
JSON.stringify(
{
ok: false,
error: `Too many chains in --chains (max ${BALANCES_MAX_CHAINS}).`
},
null,
2
)
);
process.exit(1);
}
const chainList = chainListRaw;

if (!isTTY()) {
// Non-TTY: original JSON output
const preferChainsArg = chainList.length > 0;
const singleChainSpec = preferChainsArg
? chainList[0]
: ((argv.chain as string) || undefined);
const multiChainMode = preferChainsArg && chainList.length > 1;

if (multiChainMode || (preferChainsArg && !isTTY())) {
try {
const session = await loadWalletSession(walletName);
if (!session) {
Expand All @@ -135,58 +233,93 @@ export const balancesCommand: CommandModule = {
throw new Error('Missing project access key (not in wallet session or environment)');
}

const network = resolveNetwork((argv.chain as string) || session.chain || 'polygon');
const nativeDecimals = network.nativeToken?.decimals ?? 18;
const nativeSymbol = network.nativeToken?.symbol || 'POL';

const { SequenceIndexer } = await import('@0xsequence/indexer');
const indexerUrl = getChainIndexerUrl(network.chainId);
const indexer = new SequenceIndexer(indexerUrl, indexerKey);

const [nativeRes, tokenRes] = await Promise.all([
indexer.getNativeTokenBalance({
accountAddress: session.walletAddress
}),
indexer.getTokenBalances({
accountAddress: session.walletAddress,
includeMetadata: true
})
]);
if (multiChainMode) {
const chainsOut = await Promise.all(
chainList.map((spec) =>
fetchBalancesRowsForChain(session.walletAddress, spec, indexerKey)
)
);
console.log(
JSON.stringify(
{
ok: true,
walletName,
walletAddress: session.walletAddress,
multiChain: true,
chains: chainsOut
},
bigintReplacer,
2
)
);
} else {
const one = await fetchBalancesRowsForChain(
session.walletAddress,
singleChainSpec!,
indexerKey
);
console.log(
JSON.stringify(
{
ok: true,
walletName,
walletAddress: session.walletAddress,
chainId: one.chainId,
chain: one.chain,
balances: one.balances
},
bigintReplacer,
2
)
);
}
} catch (error) {
console.error(
JSON.stringify(
{
ok: false,
error: (error as Error).message,
stack: (error as Error).stack
},
null,
2
)
);
process.exit(1);
}
return;
}

const nativeWei = nativeRes?.balance?.balance || '0';
const native = [
{
type: 'native',
symbol: nativeSymbol,
balance: formatUnits(BigInt(nativeWei), nativeDecimals)
}
];
if (!isTTY()) {
// Non-TTY: original JSON output (single default / --chain)
try {
const session = await loadWalletSession(walletName);
if (!session) {
throw new Error(`Wallet not found: ${walletName}`);
}

const erc20 = (tokenRes?.balances || []).map(
(b: {
contractInfo?: { symbol?: string; name?: string; decimals?: number };
contractAddress: string;
balance?: string;
}) => ({
type: 'erc20',
symbol: b.contractInfo?.symbol || 'ERC20',
name: b.contractInfo?.name || undefined,
contractAddress: b.contractAddress,
balance: formatUnits(b.balance || '0', b.contractInfo?.decimals ?? 18)
})
);
const indexerKey =
process.env.SEQUENCE_INDEXER_ACCESS_KEY ||
session.projectAccessKey ||
process.env.SEQUENCE_PROJECT_ACCESS_KEY;
if (!indexerKey) {
throw new Error('Missing project access key (not in wallet session or environment)');
}

const chainSpec = (argv.chain as string) || session.chain || 'polygon';
const one = await fetchBalancesRowsForChain(session.walletAddress, chainSpec, indexerKey);

console.log(
JSON.stringify(
{
ok: true,
walletName,
walletAddress: session.walletAddress,
chainId: network.chainId,
chain: network.name,
balances: [...native, ...erc20]
chainId: one.chainId,
chain: one.chain,
balances: one.balances
},
null,
bigintReplacer,
2
)
);
Expand All @@ -211,7 +344,7 @@ export const balancesCommand: CommandModule = {
await inkRender(
React.createElement(BalancesUI, {
walletName,
chainOverride: argv.chain as string | undefined
chainOverride: preferChainsArg ? singleChainSpec : (argv.chain as string | undefined)
})
);
} catch {
Expand Down
3 changes: 2 additions & 1 deletion skills/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ polygon-agent wallet remove [--name <n>]

### Operations
```bash
polygon-agent balances [--wallet <n>] [--chain <chain>]
polygon-agent balances [--wallet <n>] [--chain <chain>] [--chains <csv>]
polygon-agent send --to <addr> --amount <num> [--symbol <SYM>] [--token <addr>] [--decimals <n>] [--broadcast]
polygon-agent send-native --to <addr> --amount <num> [--broadcast] [--direct]
polygon-agent send-token --symbol <SYM> --to <addr> --amount <num> [--token <addr>] [--decimals <n>] [--broadcast]
Expand Down Expand Up @@ -127,6 +127,7 @@ polygon-agent agent feedback --agent-id <id> --value <score> [--tag1 <t>] [--tag

- **Dry-run by default** — all write commands require `--broadcast` to execute
- **Smart defaults** — `--wallet main`, `--chain polygon`, auto-wait on `wallet create`
- **`balances --chains`** — comma-separated chains (max 20); two or more return JSON with `multiChain: true` and a `chains` array (same wallet address on each)
- **Fee preference** — auto-selects USDC over native POL when both available
- **`fund`** — reads `walletAddress` from the wallet session and sets it as `toAddress` in the Trails widget URL. Always run `polygon-agent fund` to get the correct URL — never construct it manually or hardcode any address.
- **`deposit`** — picks highest-TVL pool via Trails `getEarnPools`. If session rejects (contract not whitelisted), re-create wallet with `--contract <depositAddress>`
Expand Down