Skip to content
Merged
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: 3 additions & 0 deletions .github/prompts/autonomy-version.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": "2025.11"
}
8 changes: 8 additions & 0 deletions .github/prompts/autonomy.manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"version": "2025.11",
"consent": {
"phrase": "",
"expiresMinutes": 0
},
"actions": []
}
15 changes: 15 additions & 0 deletions packages/stellar-sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@gasguard/stellar-sdk",
"version": "1.0.0",
"description": "Stellar SDK with Soroban contract interface support",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": "./dist/index.js"
},
"files": ["dist"],
"scripts": {
"build": "tsc -p tsconfig.json",
"clean": "rd /s /q dist"
}
}
1 change: 1 addition & 0 deletions packages/stellar-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './registry/interfaces';
9 changes: 9 additions & 0 deletions packages/stellar-sdk/src/registry/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export { SorobanInterfaceRegistry } from './registry';
export type {
SorobanContractInterface,
SorobanFunctionInterface,
SorobanParameter,
SorobanType,
SorobanStructInterface,
SorobanEventInterface,
} from './types';
78 changes: 78 additions & 0 deletions packages/stellar-sdk/src/registry/interfaces/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
SorobanContractInterface,
SorobanFunctionInterface,
SorobanStructInterface,
SorobanEventInterface,
} from './types';

export class SorobanInterfaceRegistry {
private contracts: Map<string, SorobanContractInterface> = new Map();
private contractIdMap: Map<string, string> = new Map();

register(contractInterface: SorobanContractInterface): void {
this.contracts.set(contractInterface.id, contractInterface);

if (contractInterface.contractId) {
this.contractIdMap.set(contractInterface.contractId, contractInterface.id);
}
}

unregister(id: string): boolean {
const contract = this.contracts.get(id);
if (contract?.contractId) {
this.contractIdMap.delete(contract.contractId);
}
return this.contracts.delete(id);
}

getById(id: string): SorobanContractInterface | undefined {
return this.contracts.get(id);
}

getByContractId(contractId: string): SorobanContractInterface | undefined {
const id = this.contractIdMap.get(contractId);
return id ? this.contracts.get(id) : undefined;
}

getAll(): SorobanContractInterface[] {
return Array.from(this.contracts.values());
}

findByName(name: string): SorobanContractInterface[] {
return this.getAll().filter(
(c) => c.name.toLowerCase() === name.toLowerCase()
);
}

findByVersion(version: string): SorobanContractInterface[] {
return this.getAll().filter((c) => c.version === version);
}

searchByName(query: string): SorobanContractInterface[] {
const lowerQuery = query.toLowerCase();
return this.getAll().filter(
(c) =>
c.name.toLowerCase().includes(lowerQuery) ||
c.metadata.description?.toLowerCase().includes(lowerQuery)
);
}

getFunction(
contractId: string,
functionName: string
): SorobanFunctionInterface | undefined {
const contract = this.getByContractId(contractId);
if (!contract) return undefined;

return contract.functions.find((f) => f.name === functionName);
}

getInterfaceCount(): number {
return this.contracts.size;
}

clear(): void {
this.contracts.clear();
this.contractIdMap.clear();
}
}
52 changes: 52 additions & 0 deletions packages/stellar-sdk/src/registry/interfaces/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export interface SorobanFunctionInterface {
name: string;
signature: string;
inputs: SorobanParameter[];
outputs: SorobanParameter[];
documentation?: string;
entryPoint?: boolean;
}

export interface SorobanParameter {
name: string;
type: SorobanType;
documentation?: string;
}

export interface SorobanType {
name: string;
isPrimitive: boolean;
isVec?: boolean;
isOption?: boolean;
isMap?: boolean;
innerType?: SorobanType;
}

export interface SorobanContractInterface {
id: string;
name: string;
version: string;
contractId?: string;
functions: SorobanFunctionInterface[];
structs: SorobanStructInterface[];
events: SorobanEventInterface[];
metadata: {
author?: string;
description?: string;
license?: string;
sourceUrl?: string;
};
}

export interface SorobanStructInterface {
name: string;
fields: SorobanParameter[];
documentation?: string;
}

export interface SorobanEventInterface {
name: string;
topics: string[];
data: SorobanParameter[];
documentation?: string;
}
8 changes: 8 additions & 0 deletions packages/stellar-sdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../packages/tsconfig/base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
111 changes: 111 additions & 0 deletions src/streaming/stellar/event-stream.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { StellarEventStream, StellarEvent, EventStreamOptions } from './event-stream';
import { RpcClient } from '@rpc/index';

describe('StellarEventStream', () => {
let eventStream: StellarEventStream;
let mockRpcClient: jest.Mocked<RpcClient>;

beforeEach(() => {
mockRpcClient = {
call: jest.fn(),
} as any;

eventStream = new StellarEventStream(mockRpcClient as RpcClient);
});

afterEach(() => {
eventStream.dispose();
});

describe('subscribe', () => {
it('should subscribe to events and return a subscription', async () => {
mockRpcClient.call.mockResolvedValue({ events: [] });

const subscription = eventStream.subscribe(
{ contractId: 'test_contract' },
jest.fn()
);

expect(subscription.id).toBeDefined();
expect(subscription.unsubscribe).toBeInstanceOf(Function);
});

it('should call callback with normalized events', async () => {
const mockEvent = {
type: 'ContractEvent',
contractId: 'test_contract',
topic: ['topic1'],
value: { data: 'test' },
ledgerSequence: 100,
};

mockRpcClient.call.mockResolvedValue({ events: [mockEvent] });

const callback = jest.fn();
const subscription = eventStream.subscribe({}, callback);

await new Promise((resolve) => setTimeout(resolve, 100));

expect(mockRpcClient.call).toHaveBeenCalledWith('getEvents', expect.any(Object));
subscription.unsubscribe();
});

it('should support filtering by contractId', async () => {
mockRpcClient.call.mockResolvedValue({ events: [] });

eventStream.subscribe({ contractId: 'specific_contract' }, jest.fn());

await new Promise((resolve) => setTimeout(resolve, 100));

expect(mockRpcClient.call).toHaveBeenCalledWith(
'getEvents',
expect.objectContaining({
filters: [{ contractId: 'specific_contract' }],
})
);

eventStream.dispose();
});

it('should support filtering by topic', async () => {
mockRpcClient.call.mockResolvedValue({ events: [] });

eventStream.subscribe({ topic: 'transfer' }, jest.fn());

await new Promise((resolve) => setTimeout(resolve, 100));

expect(mockRpcClient.call).toHaveBeenCalledWith(
'getEvents',
expect.objectContaining({
topics: [['transfer']],
})
);

eventStream.dispose();
});
});

describe('unsubscribe', () => {
it('should remove subscription when unsubscribed', () => {
mockRpcClient.call.mockResolvedValue({ events: [] });

const subscription = eventStream.subscribe({}, jest.fn());
subscription.unsubscribe();

eventStream.dispose();

expect(subscription.id).toBeDefined();
});
});

describe('dispose', () => {
it('should clear all subscriptions', () => {
mockRpcClient.call.mockResolvedValue({ events: [] });

eventStream.subscribe({ contractId: 'contract1' }, jest.fn());
eventStream.subscribe({ contractId: 'contract2' }, jest.fn());

eventStream.dispose();
});
});
});
Loading
Loading