Skip to content

feat: build full Soroban contract client service layer - #19

Merged
ijeoma270 merged 10 commits into
SoroProtocol:mainfrom
OpenSourceCOntr:feat/contract-service-layer
Aug 20, 2026
Merged

feat: build full Soroban contract client service layer#19
ijeoma270 merged 10 commits into
SoroProtocol:mainfrom
OpenSourceCOntr:feat/contract-service-layer

Conversation

@Uchechukwu-Ekezie

@Uchechukwu-Ekezie Uchechukwu-Ekezie commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Closes #14

What changed

  • Added @stellar/stellar-sdk dependency and network config resolution (testnet/mainnet)
  • Added typed error classes (ContractError, ContractErrorCode) with error parsing from SDK responses
  • Added contract constants (contract IDs, timeouts, base fee) loaded from env vars
  • Built core Soroban contract client handling the full lifecycle: build XDR, simulate, sign with Freighter, submit, poll for confirmation
  • Added stream contract methods: createStream, withdrawStream, cancelStream, getStreamBalance, getStream
  • Added vesting contract methods: createVestingSchedule, claimVesting, revokeVesting, getVestedOf, getVestingSchedule
  • Added distributor contract methods: distribute, distributeCustom
  • Added useContract React hook for loading/error state management
  • Wired real contract calls into create stream, stream detail, and vesting pages (replacing fake setTimeout/toast handlers)
  • Added vitest unit tests for error classes, error parsing, network config, and constants

Why

Every on-chain button in the app was faked with setTimeout and toast-only handlers. This adds a proper contract service layer that handles the full Soroban transaction lifecycle, enabling real on-chain interactions.

How to test

  1. npm install && npm run build — should compile without errors
  2. npx vitest run — 18 unit tests should pass
  3. Manual: connect Freighter on testnet, create a stream, verify the contract call goes through

Add the Stellar SDK as a dependency for Soroban contract interactions.
Create network.ts with testnet/mainnet config resolution from env vars.
Update .env.example with SOROBAN_RPC_URL and DISTRIBUTOR_CONTRACT_ID.

Refs: SoroProtocol#14
Create ContractError with typed error codes matching the contract's
error enum (NotFound, Unauthorized, AlreadyCancelled, etc.).
Add parseContractError() to map raw SDK/Soroban errors into typed
exceptions so the UI can show meaningful user-facing messages.

Refs: SoroProtocol#14
Add contract ID constants from env vars, shared config values, and
TypeScript interfaces for all contract function args/returns (stream,
vesting, distributor). Amounts use string to preserve bigint precision.

Refs: SoroProtocol#14
Implement the full transaction lifecycle: build XDR, simulate to catch
errors before Freighter pops up, sign via Freighter, submit, and poll
for confirmation. Includes typed error handling, network-aware RPC
client, and a high-level executeContractTx() helper.

Refs: SoroProtocol#14
Implement create_stream, withdraw, cancel (write methods with full
sign flow), and balance_of, get_stream (read-only simulation calls).
Each method encodes the correct Soroban ScVal arguments.

Refs: SoroProtocol#14
Implement vesting contract methods (create, claim, revoke, vested_of,
get_schedule) and distributor methods (distribute, distribute_custom).
Add barrel export index.ts for clean imports.

Refs: SoroProtocol#14
Provide a clean React interface for contract calls with automatic
loading state, error parsing (typed ContractError codes), and
clearError utility. Wraps the contract service layer for use in
page components.

Refs: SoroProtocol#14
… vesting

Replace all fake handlers with real contract interactions:
- Create stream: builds rate/amount from form, calls createStream
- Withdraw: calls withdrawStream with Freighter confirmation
- Cancel: calls cancelStream with Freighter confirmation
- Claim vesting: calls claimVesting with Freighter confirmation

All pages now show loading states, error messages from typed
ContractError codes, and success toasts with truncated tx hashes.

Refs: SoroProtocol#14
Covers ContractError, parseContractError, network config, and
contract constants validation. Adds vitest as devDependency.

Issue SoroProtocol#14
@ijeoma270

Copy link
Copy Markdown
Contributor

went through the diff — the error types and the useContract hook are clean, good call on simulating before popping up Freighter. few things that'll bite you though:

the method names in streams.ts don't match the actual contract. the rust code has create, withdraw, cancel — you're calling create_stream, withdraw_stream, cancel_stream. soroban will just reject those.

createStream is passing the wrong args too. contract expects (sender, recipient, token, rate_per_second, start_time, stop_time) — six args, rate as u64. you're sending five args with a total amount as i128. also sender needs to be first since the contract uses it for auth checks.

token encoding — you're doing toScValBytes32(args.token) everywhere but the contract takes a Stellar address, not raw bytes. should be addressToScVal instead. same issue in distributor.ts.

the testnet passphrase in network.ts says December 2022 — it's actually December 2014. every signature will be invalid on testnet with that.

and buildContractTx builds a raw TransactionBuilder but doesn't attach the soroban auth data from simulation. you need assembleTransaction or prepareTransaction after simulation, otherwise the signed tx won't have the right footprint/auth entries and it'll fail on-chain.

CI build is failing too — probably related to the type mismatches from the wrong arg shapes.

the overall structure is solid though, just needs the contract details lined up with the actual rust code.

- Fix contract method name: create_stream → create
- Fix createStream args: add sender, use ratePerSecond instead of amount
- Fix token encoding: use Address.fromString().toScVal() instead of bytes32
- Fix testnet passphrase: December 2022 → December 2014
- Fix Soroban tx assembly: use rpc.assembleTransaction() after simulation
- Fix SDK API: SorobanRpc → rpc namespace, loadAccount → getAccount
- Fix simulation result extraction: use result.retval directly

Refs: SoroProtocol#14
@Uchechukwu-Ekezie

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — all 6 points addressed:

  1. Method namescreate_streamcreate (others were already correct)
  2. createStream args — now passes (sender, recipient, token, rate_per_second, start_time, stop_time) matching the contract signature
  3. Token encoding — changed from nativeToScVal(token, {type:'bytes'}) to Address.fromString(token).toScVal() in streams, vesting, and distributor
  4. Testnet passphrase — fixed to Test Soro Network ; December 2014
  5. Soroban tx assemblysimulateTx now calls rpc.assembleTransaction() after simulation, returning the assembled XDR for signing
  6. Simulation result extraction — uses response.result.retval directly instead of manually parsing TransactionResult XDR

Also fixed SDK namespace (SorobanRpcrpc) and account loading (loadAccountgetAccount) to match the installed SDK version.

TypeScript compiles clean, all 18 unit tests pass.

@ijeoma270

Copy link
Copy Markdown
Contributor

LGTM, thanks for contributing

@ijeoma270
ijeoma270 merged commit 1db30ff into SoroProtocol:main Aug 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: build full Soroban contract client service layer

2 participants