Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9cd7cdc
feat: Add invoke command for executing Stellar smart contracts
joelorzet May 15, 2025
d9cfbc4
feat: Implement validation service for Soroban smart contracts
joelorzet May 15, 2025
9d87656
feat: Add IInvokeCommandArgs interface for invoking Stellar smart con…
joelorzet May 15, 2025
200e336
feat: Add IInvokeContractMethod interface for invoking contract metho…
joelorzet May 15, 2025
7b15f41
feat: Add InvokeContractMethodSchema for invoking contract methods wi…
joelorzet May 15, 2025
55d7981
fix: Update import path for ContractInterface
joelorzet May 15, 2025
6a0e01d
feat: Add soroban_invoke_contract_method tool for invoking Soroban co…
joelorzet May 15, 2025
3c519e0
feat: Add ICommandResult interface for command execution results
joelorzet May 16, 2025
21d78bc
feat: Implement executeCommand method for executing shell commands in…
joelorzet May 16, 2025
9bb904e
feat: Enhance Soroban class with contract invocation and validation m…
joelorzet May 16, 2025
358f26b
test: Add unit tests for SorobanValidationService to validate contrac…
joelorzet May 19, 2025
990bdf9
docs: Update README to include detailed documentation for soroban_inv…
joelorzet May 19, 2025
50da78e
feat: Introduce error handling interfaces and methods in Soroban and …
joelorzet May 19, 2025
41790d4
fix: Update import paths in validation service and tests to include .…
joelorzet May 19, 2025
ccbe68e
fix: Improve error handling in Soroban class to include detailed buil…
joelorzet May 22, 2025
d9b5dd1
feat: Add support for invoking contract methods in handleToolCall fun…
joelorzet May 22, 2025
b41b94c
Merge branch 'main' into feat/invoke-smart-contract-method
joelorzet Jun 23, 2025
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
103 changes: 103 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,109 @@ A Model Context Protocol server that provides Stellar blockchain interaction cap
}
```

- **soroban_invoke_contract_method**

- Invoke a method on a deployed Soroban smart contract
- Inputs:
- `contractAddress` (string, required): Address of the deployed contract (starts with "C")
- `method` (object, required): The method to invoke
- `name` (string): Name of the method
- `parameters` (array): Array of parameters with:
- `name` (string): Parameter name
- `type` (string): Parameter type
- `returnType` (string): Return type of the method
- `args` (array, optional): Arguments for the method
- `name` (string): Name of the argument
- `type` (string): Type of the argument
- `value` (string): Value of the argument
- `secretKey` (string, required): Secret key of the account to sign the transaction
- `structs` (array, optional): Array of contract structs
- `name` (string): Struct name
- `fields` (array): Array of fields with:
- `name` (string): Field name
- `type` (string): Field type
- `visibility` (string): Field visibility
- `enums` (array, optional): Array of contract enums
- `name` (string): Enum name
- `variants` (array): Array of variants with:
- `name` (string): Variant name
- `value` (number): Variant value
- `dataType` (string): Variant data type
- `isError` (boolean): Whether the enum is an error
- Outputs:
- Transaction execution status
- Method invocation result
- Any error messages if the invocation fails
- Features:
- Validates method parameters before invocation
- Supports all Soroban data types
- Handles complex data structures (structs, enums)
- Provides detailed error messages for invalid invocations
- Example Usage:

```typescript
// Invoking a simple method
await soroban.invokeContractMethod({
contractAddress:
'CACLOQNDBVG2Q7VRQGOKC4THZ34FHW2PUYQQOAVBSLJEV6VHEF3ZCIPO',
method: {
name: 'get_admin',
parameters: [],
returnType: 'Address',
},
secretKey: 'S...',
});

// Invoking a method with arguments
await soroban.invokeContractMethod({
contractAddress:
'CACLOQNDBVG2Q7VRQGOKC4THZ34FHW2PUYQQOAVBSLJEV6VHEF3ZCIPO',
method: {
name: 'set_admin',
parameters: [{ name: 'admin', type: 'Address' }],
returnType: '()',
},
args: [{ name: 'admin', type: 'Address', value: 'G...' }],
secretKey: 'S...',
});

// Invoking a method with complex data types
await soroban.invokeContractMethod({
contractAddress:
'CACLOQNDBVG2Q7VRQGOKC4THZ34FHW2PUYQQOAVBSLJEV6VHEF3ZCIPO',
method: {
name: 'handle_complex',
parameters: [{ name: 'data', type: 'ComplexData' }],
returnType: '(Data, ComplexData)',
},
args: [
{
name: 'data',
type: 'ComplexData',
value:
'{"admin":"G...","data":{"admin":"G...","counter":1,"message":"test"},"bytes":"base64...","bytes_n":"base64...","duration":3600,"map":{"key":1},"symbol":"TEST","timepoint":1234567890,"vec":[1,2,3]}',
},
],
structs: [
{
name: 'ComplexData',
fields: [
{ name: 'admin', type: 'Address', visibility: 'pub' },
{ name: 'data', type: 'Data', visibility: 'pub' },
{ name: 'bytes', type: 'Bytes', visibility: 'pub' },
{ name: 'bytes_n', type: 'BytesN<32>', visibility: 'pub' },
{ name: 'duration', type: 'Duration', visibility: 'pub' },
{ name: 'map', type: 'Map<String, u32>', visibility: 'pub' },
{ name: 'symbol', type: 'Symbol', visibility: 'pub' },
{ name: 'timepoint', type: 'Timepoint', visibility: 'pub' },
{ name: 'vec', type: 'Vec<u32>', visibility: 'pub' },
],
},
],
secretKey: 'S...',
});
```

## ⭐ Key Features

- 👤 Account management (creation, funding, balance checking)
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ async function handleToolCall(
return {
content: retrieveContractMethods,
};
case 'soroban_invoke_contract_method':
const invokeContractMethod = await soroban.invokeContractMethod(args);

return {
content: invokeContractMethod,
};
default:
throw new Error(`Tool ${name} not found`);
}
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces/common.interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ExecException } from 'child_process';

export type OutputMessage = {
type: 'text';
text: string;
Expand All @@ -14,3 +16,9 @@ export enum Platform {
LINUX = 'linux',
MACOS = 'darwin',
}

export interface ICommandResult {
error: ExecException | null;
stdout: string;
stderr: string;
}
39 changes: 39 additions & 0 deletions src/interfaces/services/validate.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export enum ValidationErrorType {
MISSING_METHOD_PARAM = 'MISSING_METHOD_PARAM',
EXTRA_METHOD_PARAM = 'EXTRA_METHOD_PARAM',
INVALID_PARAM_TYPE = 'INVALID_PARAM_TYPE',
MISSING_STRUCT_FIELD = 'MISSING_STRUCT_FIELD',
EXTRA_STRUCT_FIELD = 'EXTRA_STRUCT_FIELD',
INVALID_STRUCT_TYPE = 'INVALID_STRUCT_TYPE',
MISSING_NESTED_STRUCT = 'MISSING_NESTED_STRUCT',
STRUCT_NOT_FOUND = 'STRUCT_NOT_FOUND',
}

export enum ValidationContext {
METHOD_PARAMETERS = 'method_parameters',
STRUCT_TYPE = 'struct_type',
STRUCT_DEFINITION = 'struct_definition',
STRUCT_FIELDS = 'struct_fields',
NESTED_STRUCT = 'nested_struct',
}

export type ValidationError = {
type: ValidationErrorType;
message: string;
details: {
paramName?: string;
methodName?: string;
structName?: string;
fieldName?: string;
expectedType?: string;
providedType?: string;
missingFields?: string[];
extraFields?: string[];
context: ValidationContext;
};
};

export type Parameter = {
name: string;
type: string;
};
14 changes: 12 additions & 2 deletions src/interfaces/soroban/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ export interface IContractInterfaceArgs {
contractId: string;
network?: string;
}
export interface IInvokeCommandArgs {
contractAddress: string;
method: string;
args: string[];
secretKey: string;
network?: string;
}

export type CommandArgsMap = {
find: IFindCommandArgs;
Expand All @@ -35,6 +42,7 @@ export type CommandArgsMap = {
optimize: IOptimizeCommandArgs;
deploy: IDeployCommandArgs;
contractInterface: IContractInterfaceArgs;
invoke: IInvokeCommandArgs;
};

export type CommandArgs =
Expand All @@ -43,12 +51,14 @@ export type CommandArgs =
| IBuildCommandArgs
| IOptimizeCommandArgs
| IDeployCommandArgs
| IContractInterfaceArgs;
| IContractInterfaceArgs
| IInvokeCommandArgs;

export type CommandName =
| 'find'
| 'dir'
| 'build'
| 'optimize'
| 'deploy'
| 'contractInterface';
| 'contractInterface'
| 'invoke';
29 changes: 29 additions & 0 deletions src/interfaces/soroban/Error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export enum ErrorType {
BUILD = 'build',
OPTIMIZE = 'optimize',
DEPLOY = 'deploy',
}

export interface IError {
type: ErrorType;
}

export interface IBuildError extends IError {
type: ErrorType.BUILD;
stdout: string;
stderr: string;
}

export interface IOptimizeError extends IError {
type: ErrorType.OPTIMIZE;
stdout: string;
stderr: string;
}

export interface IDeployError extends IError {
type: ErrorType.DEPLOY;
stdout: string;
stderr: string;
}

export type ExecutionError = IBuildError | IOptimizeError | IDeployError;
14 changes: 14 additions & 0 deletions src/interfaces/soroban/InvokeContractMethod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IContractMethod, IContractParameter } from './ContractInterface';
import { IContractInterface } from './ContractInterface';

export interface IInvokeContractMethodArgs extends IContractParameter {
value: string;
}

export interface IInvokeContractMethod
extends Pick<IContractInterface, 'structs' | 'enums'> {
contractAddress: string;
method: IContractMethod;
args?: IInvokeContractMethodArgs[];
secretKey: string;
}
43 changes: 42 additions & 1 deletion src/stellar/core/core.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Platform } from '../../interfaces/common.interface.js';
import { ExecException, exec } from 'child_process';

import { ICommandResult, Platform } from '../../interfaces/common.interface.js';
import {
CommandArgsMap,
CommandName,
Expand All @@ -7,8 +9,10 @@ import {
IDeployCommandArgs,
IDirCommandArgs,
IFindCommandArgs,
IInvokeCommandArgs,
IOptimizeCommandArgs,
} from '../../interfaces/soroban/Commands.js';
import { ErrorType, ExecutionError } from '../../interfaces/soroban/Error.js';
import { MessagesManager } from './messages.js';

export class Core extends MessagesManager {
Expand Down Expand Up @@ -49,6 +53,8 @@ export class Core extends MessagesManager {
`stellar contract deploy --wasm "${args.wasmPath}" --source "${args.secretKey}" --network ${args.network || 'testnet'} ${args.constructorArgs?.length ? `-- ${args.constructorArgs}` : ''}`,
contractInterface: (args: IContractInterfaceArgs) =>
`stellar contract info interface --network ${args.network || 'testnet'} --id ${args.contractId}`,
invoke: (args: IInvokeCommandArgs) =>
`stellar contract invoke --network ${args.network || 'testnet'} --source "${args.secretKey}" --send=yes --id ${args.contractAddress} -- ${args.method} ${args.args.join(' ')}`,
};
}

Expand All @@ -68,6 +74,41 @@ export class Core extends MessagesManager {
`stellar contract deploy --wasm "${args.wasmPath}" --source "${args.secretKey}" --network ${args.network || 'testnet'} ${args.constructorArgs?.length ? `-- ${args.constructorArgs}` : ''}`,
contractInterface: (args: IContractInterfaceArgs) =>
`stellar contract info interface --network ${args.network || 'testnet'} --id ${args.contractId}`,
invoke: (args: IInvokeCommandArgs) =>
`stellar contract invoke --network ${args.network || 'testnet'} --source "${args.secretKey}" --send=yes --id ${args.contractAddress} -- ${args.method} ${args.args.join(' ')}`,
};
}

protected async executeCommand(command: string): Promise<ICommandResult> {
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
resolve({ error, stdout, stderr });
});
});
}

protected handleCommandError(
type: ErrorType,
{
error,
stdout,
stderr,
}: {
error: ExecException | null;
stdout: string;
stderr: string;
},
): ExecutionError {
if (error) {
console.error('Error:', error);
if (
this.platform !== Platform.WINDOWS &&
!stderr.includes('could not find `Cargo.toml`')
) {
stderr = 'The system cannot find the path specified';
}
}

return { type, stdout, stderr };
}
}
Loading
Loading