@andrewkimjoseph/celina-sdk 0.2.10 → 0.2.12

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.
@@ -0,0 +1,111 @@
1
+ import { parseEther } from "viem";
2
+ import type { OperationSpec } from "../types.js";
3
+ import { assertHasKeys } from "../../helpers/assert.js";
4
+
5
+ function fromAddress(fx: Parameters<OperationSpec["assert"]>[1]): `0x${string}` {
6
+ return fx.signerAddress ?? fx.wallet;
7
+ }
8
+
9
+ export const transactionOperations: OperationSpec[] = [
10
+ {
11
+ id: "transaction.getGasFeeData",
12
+ domain: "transaction",
13
+ layer: "read",
14
+ sdk: {
15
+ invoke: (client) => client.transaction.getGasFeeData(),
16
+ },
17
+ mcp: {
18
+ tool: "get_gas_fee_data",
19
+ arguments: () => ({}),
20
+ },
21
+ assert: (result) => {
22
+ assertHasKeys(result, ["network"]);
23
+ },
24
+ },
25
+ {
26
+ id: "transaction.estimateTransaction",
27
+ domain: "transaction",
28
+ layer: "read",
29
+ requiresEnv: ["CELO_PRIVATE_KEY"],
30
+ sdk: {
31
+ invoke: (client, fx) =>
32
+ client.transaction.estimateTransaction({
33
+ from: fromAddress(fx),
34
+ to: fx.wallet,
35
+ value: parseEther("0.001").toString(),
36
+ }),
37
+ },
38
+ mcp: {
39
+ tool: "estimate_transaction",
40
+ arguments: (fx) => ({
41
+ from: fromAddress(fx),
42
+ to: fx.wallet,
43
+ value: parseEther("0.001").toString(),
44
+ }),
45
+ },
46
+ assert: (result) => {
47
+ assertHasKeys(result, ["gasLimit"]);
48
+ },
49
+ },
50
+ {
51
+ id: "transaction.estimateSend",
52
+ domain: "transaction",
53
+ layer: "read",
54
+ requiresEnv: ["CELO_PRIVATE_KEY"],
55
+ sdk: {
56
+ invoke: (client, fx) =>
57
+ client.transaction.estimateSend(
58
+ fromAddress(fx),
59
+ fx.wallet,
60
+ "CELO",
61
+ "0.001",
62
+ ),
63
+ },
64
+ mcp: {
65
+ tool: "estimate_send",
66
+ arguments: () => ({
67
+ to: "0x471EcE3750Da237f93B8E339c536989b8978a438",
68
+ token: "CELO",
69
+ amount: "0.001",
70
+ }),
71
+ },
72
+ assert: (result) => {
73
+ assertHasKeys(result, ["gas"]);
74
+ },
75
+ },
76
+ {
77
+ id: "transaction.prepareSend",
78
+ domain: "transaction",
79
+ layer: "prepare",
80
+ sdk: {
81
+ invoke: (client, fx) =>
82
+ client.transaction.prepareSend(
83
+ fromAddress(fx),
84
+ fx.wallet,
85
+ "CELO",
86
+ "0.001",
87
+ ),
88
+ },
89
+ assert: (result) => {
90
+ assertHasKeys(result, ["from", "steps", "summary"]);
91
+ },
92
+ },
93
+ {
94
+ id: "transaction.sendToken",
95
+ domain: "transaction",
96
+ layer: "write",
97
+ requiresEnv: ["CELO_PRIVATE_KEY"],
98
+ requiresWrites: true,
99
+ mcp: {
100
+ tool: "send_token",
101
+ arguments: () => ({
102
+ to: "0x471EcE3750Da237f93B8E339c536989b8978a438",
103
+ token: "CELO",
104
+ amount: "0.000001",
105
+ }),
106
+ },
107
+ assert: (result) => {
108
+ assertHasKeys(result, ["hash"]);
109
+ },
110
+ },
111
+ ];
@@ -0,0 +1,7 @@
1
+ export type { OperationSpec, OperationLayer, EnvRequirement } from "./types.js";
2
+ export {
3
+ OPERATIONS,
4
+ MCP_OPERATIONS,
5
+ SDK_OPERATIONS,
6
+ MCP_TOOL_NAMES,
7
+ } from "./operations.js";
@@ -0,0 +1,46 @@
1
+ import { blockchainOperations } from "./domains/blockchain.js";
2
+ import { tokenOperations } from "./domains/token.js";
3
+ import { transactionOperations } from "./domains/transaction.js";
4
+ import {
5
+ aaveOperations,
6
+ ensOperations,
7
+ gooddollarOperations,
8
+ mentoFxOperations,
9
+ uniswapOperations,
10
+ } from "./domains/defi.js";
11
+ import {
12
+ contractOperations,
13
+ governanceOperations,
14
+ nftOperations,
15
+ stakingOperations,
16
+ } from "./domains/chain-ext.js";
17
+ import { selfOperations } from "./domains/self.js";
18
+ import type { OperationSpec } from "./types.js";
19
+
20
+ export const OPERATIONS: OperationSpec[] = [
21
+ ...blockchainOperations,
22
+ ...tokenOperations,
23
+ ...transactionOperations,
24
+ ...mentoFxOperations,
25
+ ...uniswapOperations,
26
+ ...aaveOperations,
27
+ ...ensOperations,
28
+ ...gooddollarOperations,
29
+ ...governanceOperations,
30
+ ...stakingOperations,
31
+ ...nftOperations,
32
+ ...contractOperations,
33
+ ...selfOperations,
34
+ ];
35
+
36
+ export const MCP_OPERATIONS = OPERATIONS.filter(
37
+ (spec): spec is OperationSpec & { mcp: NonNullable<OperationSpec["mcp"]> } =>
38
+ Boolean(spec.mcp),
39
+ );
40
+
41
+ export const SDK_OPERATIONS = OPERATIONS.filter(
42
+ (spec): spec is OperationSpec & { sdk: NonNullable<OperationSpec["sdk"]> } =>
43
+ Boolean(spec.sdk),
44
+ );
45
+
46
+ export const MCP_TOOL_NAMES = MCP_OPERATIONS.map((spec) => spec.mcp.tool);
@@ -0,0 +1,27 @@
1
+ import type { CelinaClient } from "@andrewkimjoseph/celina-sdk";
2
+ import type { MainnetFixtures } from "../fixtures/mainnet.js";
3
+
4
+ export type OperationLayer = "read" | "write" | "prepare";
5
+
6
+ export type EnvRequirement = "CELO_PRIVATE_KEY" | "SELF_AGENT_PRIVATE_KEY";
7
+
8
+ export interface OperationSpec {
9
+ /** Stable id, e.g. `token.getTokenBalance`. */
10
+ id: string;
11
+ domain: string;
12
+ layer: OperationLayer;
13
+ requiresEnv?: EnvRequirement[];
14
+ /** On-chain writes (`send_token`, `execute_mento_fx`, Aave supply/withdraw). */
15
+ requiresWrites?: boolean;
16
+ /** Self register / deregister / refresh flows. */
17
+ requiresDestructive?: boolean;
18
+ sdk?: {
19
+ invoke: (client: CelinaClient, fx: MainnetFixtures) => Promise<unknown>;
20
+ };
21
+ mcp?: {
22
+ tool: string;
23
+ arguments: (fx: MainnetFixtures) => Record<string, unknown>;
24
+ };
25
+ assert: (result: unknown, fx: MainnetFixtures) => void;
26
+ skip?: () => string | undefined;
27
+ }
@@ -0,0 +1,93 @@
1
+ import type { CelinaClient } from "@andrewkimjoseph/celina-sdk";
2
+ import { getSignerAddress } from "../helpers/env.js";
3
+
4
+ export interface MainnetFixtures {
5
+ wallet: `0x${string}`;
6
+ usdm: `0x${string}`;
7
+ saidContract: `0x${string}`;
8
+ saidOwner: `0x${string}`;
9
+ saidTokenId: string;
10
+ validatorGroup: `0x${string}`;
11
+ proposalId: number;
12
+ ensName: string;
13
+ selfAgentId: number;
14
+ /** Known registered Self agent for read-only verification. */
15
+ selfAgentAddress: `0x${string}`;
16
+ erc20SymbolAbi: readonly [
17
+ {
18
+ readonly type: "function";
19
+ readonly name: "symbol";
20
+ readonly stateMutability: "view";
21
+ readonly inputs: readonly [];
22
+ readonly outputs: readonly [{ readonly type: "string" }];
23
+ },
24
+ ];
25
+ knownTxHash: `0x${string}`;
26
+ latestBlockNumber: bigint;
27
+ signerAddress?: `0x${string}`;
28
+ /** Populated by MCP enrichment when SELF_AGENT_PRIVATE_KEY is set. */
29
+ selfVerifyRequestArgs?: Record<string, unknown>;
30
+ }
31
+
32
+ export const MAINNET_STATIC = {
33
+ wallet: "0x471EcE3750Da237f93B8E339c536989b8978a438" as const,
34
+ usdm: "0x765DE816845861e75A25fCA122bb6898B8B1282a" as const,
35
+ saidContract: "0xaC3DF9ABf80d0F5c020C06B04Cced27763355944" as const,
36
+ saidOwner: "0x62fD20ca524C13Ce836Def1c0FF8e5119476868D" as const,
37
+ saidTokenId: "1",
38
+ validatorGroup: "0x0861a61Bf679A30680510EcC238ee43B82C5e843" as const,
39
+ proposalId: 293,
40
+ ensName: "celina.eth",
41
+ selfAgentId: 1,
42
+ selfAgentAddress: "0xC1C860804EFdA544fe79194d1a37e60b846CEdeb" as const,
43
+ erc20SymbolAbi: [
44
+ {
45
+ type: "function",
46
+ name: "symbol",
47
+ stateMutability: "view",
48
+ inputs: [],
49
+ outputs: [{ type: "string" }],
50
+ },
51
+ ] as const,
52
+ };
53
+
54
+ let cachedFixtures: MainnetFixtures | null = null;
55
+
56
+ /** Load stable mainnet fixtures, resolving a recent tx hash once per process. */
57
+ export async function getMainnetFixtures(
58
+ client: CelinaClient,
59
+ ): Promise<MainnetFixtures> {
60
+ if (cachedFixtures) {
61
+ return cachedFixtures;
62
+ }
63
+
64
+ const status = await client.blockchain.getNetworkStatus();
65
+ const latestBlockNumber = BigInt(status.blockNumber);
66
+ const block = await client.blockchain.getBlock(Number(latestBlockNumber), {
67
+ includeTransactions: true,
68
+ });
69
+
70
+ const txs = block.transactions as Array<{ hash?: `0x${string}` } | string>;
71
+ const firstTx = txs[0];
72
+ const knownTxHash =
73
+ typeof firstTx === "string"
74
+ ? (firstTx as `0x${string}`)
75
+ : (firstTx?.hash as `0x${string}`);
76
+
77
+ if (!knownTxHash) {
78
+ throw new Error("Could not resolve a mainnet transaction hash from latest block");
79
+ }
80
+
81
+ cachedFixtures = {
82
+ ...MAINNET_STATIC,
83
+ knownTxHash,
84
+ latestBlockNumber,
85
+ signerAddress: getSignerAddress(),
86
+ };
87
+
88
+ return cachedFixtures;
89
+ }
90
+
91
+ export function resetMainnetFixturesCache(): void {
92
+ cachedFixtures = null;
93
+ }
@@ -0,0 +1,30 @@
1
+ import { expect } from "vitest";
2
+
3
+ export function assertDefined(result: unknown): asserts result is NonNullable<unknown> {
4
+ expect(result).toBeDefined();
5
+ expect(result).not.toBeNull();
6
+ }
7
+
8
+ export function assertObject(result: unknown): Record<string, unknown> {
9
+ assertDefined(result);
10
+ expect(typeof result).toBe("object");
11
+ expect(Array.isArray(result)).toBe(false);
12
+ return result as Record<string, unknown>;
13
+ }
14
+
15
+ export function assertArray(result: unknown): unknown[] {
16
+ assertDefined(result);
17
+ expect(Array.isArray(result)).toBe(true);
18
+ return result as unknown[];
19
+ }
20
+
21
+ export function assertHasKeys(
22
+ result: unknown,
23
+ keys: string[],
24
+ ): Record<string, unknown> {
25
+ const obj = assertObject(result);
26
+ for (const key of keys) {
27
+ expect(obj).toHaveProperty(key);
28
+ }
29
+ return obj;
30
+ }
@@ -0,0 +1,38 @@
1
+ import { privateKeyToAccount } from "viem/accounts";
2
+
3
+ export interface TestConfig {
4
+ rpcUrl: string;
5
+ ethRpcUrl?: string;
6
+ }
7
+
8
+ export function hasCeloWallet(): boolean {
9
+ return Boolean(process.env.CELO_PRIVATE_KEY);
10
+ }
11
+
12
+ export function hasSelfAgentKey(): boolean {
13
+ return Boolean(process.env.SELF_AGENT_PRIVATE_KEY);
14
+ }
15
+
16
+ export function allowsTestWrites(): boolean {
17
+ return process.env.CELINA_TEST_WRITES === "1";
18
+ }
19
+
20
+ export function allowsDestructiveTests(): boolean {
21
+ return process.env.CELINA_TEST_DESTRUCTIVE === "1";
22
+ }
23
+
24
+ export function getSignerAddress(): `0x${string}` | undefined {
25
+ const key = process.env.CELO_PRIVATE_KEY;
26
+ if (!key) {
27
+ return undefined;
28
+ }
29
+ return privateKeyToAccount(key as `0x${string}`).address;
30
+ }
31
+
32
+ /** RPC URLs aligned with celina-mcp `loadConfig()`. */
33
+ export function loadTestConfig(): TestConfig {
34
+ return {
35
+ rpcUrl: process.env.CELO_RPC_URL_MAINNET ?? "https://forno.celo.org",
36
+ ethRpcUrl: process.env.ETH_RPC_URL_MAINNET,
37
+ };
38
+ }
@@ -0,0 +1,36 @@
1
+ import type { OperationSpec } from "../catalog/types.js";
2
+ import {
3
+ allowsDestructiveTests,
4
+ allowsTestWrites,
5
+ hasCeloWallet,
6
+ hasSelfAgentKey,
7
+ } from "./env.js";
8
+
9
+ const ENV_CHECKS: Record<
10
+ NonNullable<OperationSpec["requiresEnv"]>[number],
11
+ () => boolean
12
+ > = {
13
+ CELO_PRIVATE_KEY: hasCeloWallet,
14
+ SELF_AGENT_PRIVATE_KEY: hasSelfAgentKey,
15
+ };
16
+
17
+ /** Returns a skip reason when the operation should not run, otherwise undefined. */
18
+ export function getOperationSkipReason(spec: OperationSpec): string | undefined {
19
+ if (spec.requiresDestructive && !allowsDestructiveTests()) {
20
+ return "Set CELINA_TEST_DESTRUCTIVE=1 to run destructive Self lifecycle tests";
21
+ }
22
+
23
+ if (spec.requiresWrites && !allowsTestWrites()) {
24
+ return "Set CELINA_TEST_WRITES=1 to run on-chain write tests";
25
+ }
26
+
27
+ if (spec.requiresEnv) {
28
+ for (const requirement of spec.requiresEnv) {
29
+ if (!ENV_CHECKS[requirement]()) {
30
+ return `Missing ${requirement}`;
31
+ }
32
+ }
33
+ }
34
+
35
+ return spec.skip?.() ?? undefined;
36
+ }
@@ -0,0 +1,22 @@
1
+ export type {
2
+ EnvRequirement,
3
+ OperationLayer,
4
+ OperationSpec,
5
+ } from "./catalog/types.js";
6
+ export {
7
+ MCP_OPERATIONS,
8
+ MCP_TOOL_NAMES,
9
+ OPERATIONS,
10
+ SDK_OPERATIONS,
11
+ } from "./catalog/operations.js";
12
+ export { getMainnetFixtures, type MainnetFixtures } from "./fixtures/mainnet.js";
13
+ export {
14
+ allowsDestructiveTests,
15
+ allowsTestWrites,
16
+ getSignerAddress,
17
+ hasCeloWallet,
18
+ hasSelfAgentKey,
19
+ loadTestConfig,
20
+ type TestConfig,
21
+ } from "./helpers/env.js";
22
+ export { getOperationSkipReason } from "./helpers/gating.js";