trustflow-protocol/trustflow-sdk(新しいタブで開きます)
主要言語: TypeScript
- star 数
- 0
- watcher 数
- 0
- fork 数
- 97
- issue 数
- 169
- 最終更新
- 2026/10/30
主要言語: TypeScript
Type-safe TypeScript SDK for building gig-economy applications on the TrustFlow Protocol (Stellar/Soroban).
The TrustFlow SDK provides a developer-friendly interface for interacting with TrustFlow smart contracts on the Stellar network. Build escrow systems, dispute resolution platforms, and decentralized freelance marketplaces with clean, type-safe APIs.
npm install @trustflow/sdk
# or
yarn add @trustflow/sdkimport { TrustFlowClient } from '@trustflow/sdk'; const client = new TrustFlowClient({ contractId: process.env.TRUSTFLOW_CONTRACT_ID!, network: 'TESTNET', // or 'MAINNET' }); await client.connect(); console.log('Connected to', client.network);
import { TrustFlowEscrowClient, EscrowBuilder } from '@trustflow/sdk'; const escrowClient = new TrustFlowEscrowClient({ contractId: process.env.TRUSTFLOW_CONTRACT_ID!, network: 'TESTNET', rpcUrl: 'https://soroban-testnet.stellar.org', networkPassphrase: 'Test SDF Network ; September 2015', }); const params = new EscrowBuilder() .setDepositor('GDEPOSITOR...') .setBeneficiary('GBENEFICIARY...') .setAmount('50') // XLM .setDeadline(17280) // ~1 day in ledgers .build(); const result = await escrowClient.createEscrow(params); if (result.ok) { console.log('Escrow ID:', result.data.escrowId); console.log('Tx Hash:', result.data.txHash); }
import { TrustFlowClient, TrustFlowEscrowClient } from '@trustflow/sdk'; import { createEscrow, releaseEscrow } from '@trustflow/sdk/escrow'; import { connectWallet } from '@trustflow/sdk/wallet'; import { xlmToStroops } from '@trustflow/sdk/utils'; const wallet = await connectWallet('freighter'); const client = new TrustFlowClient({ contractId: process.env.TRUSTFLOW_CONTRACT_ID!, network: 'TESTNET', }); await client.connect(); // Create const escrow = await createEscrow(client, { sender: wallet.publicKey, recipient: 'GRECIPIENT...', amountStroops: xlmToStroops('50'), durationBlocks: 17280, metadata: { orderId: 'ORD-001' }, }); console.log('Escrow created:', escrow.id); // Fund (e.g. lock USDC via its Soroban token contract instead of the native asset) const escrowClient = new TrustFlowEscrowClient({ contractId: process.env.TRUSTFLOW_CONTRACT_ID!, network: 'TESTNET', rpcUrl: 'https://soroban-testnet.stellar.org', networkPassphrase: 'Test SDF Network ; September 2015', }); const funded = await escrowClient.fund( escrow.id, wallet.publicKey, xlmToStroops('50'), process.env.USDC_CONTRACT_ID, ); if (funded.ok) console.log('Funded! tx:', funded.data.txHash); // Release const txHash = await releaseEscrow(client, { escrowId: escrow.id, caller: wallet.publicKey, }); console.log('Released! Transaction:', txHash);
See docs/QUICKSTART.md for the full walkthrough including disputes, multi-sig, and pagination.
Collect signatures from multiple approvers before a release is broadcast:
import { MultiSigEscrowClient } from '@trustflow/sdk'; import { Networks } from '@stellar/stellar-sdk'; const client = new MultiSigEscrowClient({ contractId: process.env.TRUSTFLOW_CONTRACT_ID!, network: 'TESTNET', rpcUrl: 'https://soroban-testnet.stellar.org', networkPassphrase: Networks.TESTNET, }); // Register a 2-of-2 release operation const { data: { operationId } } = client.initMultiSigOperation({ escrowId: 'esc-42', signers: [APPROVER_A, APPROVER_B], threshold: 2, operationType: 'release', unsignedXdr: UNSIGNED_RELEASE_XDR, networkPassphrase: Networks.TESTNET, }); // Each approver submits their signed XDR independently client.addSignature({ operationId, signerAddress: APPROVER_A, signedXdr: SIGNED_XDR_A }); client.addSignature({ operationId, signerAddress: APPROVER_B, signedXdr: SIGNED_XDR_B }); // Broadcast once threshold is met const result = await client.submitWhenReady(operationId, 'https://horizon-testnet.stellar.org'); console.log('Released! tx:', result.data?.txHash);
See examples/multisig-escrow.ts for the full walkthrough.
Cast a juror's vote on a dispute, either in the open or as ciphertext (e.g. for a commit-reveal
scheme — the SDK does not perform the encryption itself, ciphertext must already be
base64-encoded by the caller):
import { JurorClient } from '@trustflow/sdk'; const jurors = new JurorClient({ contractId: process.env.TRUSTFLOW_CONTRACT_ID!, network: 'TESTNET', rpcUrl: 'https://soroban-testnet.stellar.org', networkPassphrase: 'Test SDF Network ; September 2015', }); // Plaintext vote const result = await jurors.vote({ disputeId: 'dsp-1', jurorAddress: 'GJUROR...', vote: { encrypted: false, choice: 'approve' }, }); // Encrypted vote (commit-reveal style) const encryptedResult = await jurors.vote({ disputeId: 'dsp-1', jurorAddress: 'GJUROR...', vote: { encrypted: true, ciphertext: myCiphertext.toString('base64') }, }); if (result.ok) console.log('Voted! tx:', result.data.txHash);
Once an escrow has cleared for release, the beneficiary can withdraw funds directly with
claim — a shortcut that doesn't require a separate release step from the depositor:
const result = await escrowClient.claim('esc-42', 'GBENEFICIARY...'); if (result.ok) console.log('Claimed! tx:', result.data.txHash);
ProfileClient wraps the backend's /profiles endpoints with the same retry-aware transport
as DisputeClient/JurorClient:
import { ProfileClient } from '@trustflow/sdk'; const profiles = new ProfileClient(process.env.TRUSTFLOW_API_URL!, authToken); const result = await profiles.getProfile(wallet.publicKey); if (result.ok) console.log(result.data.displayName); await profiles.updateProfile(wallet.publicKey, { bio: 'Building on Stellar' });
Every TrustFlowClient exposes a built-in storage.upload() helper for pinning files to
IPFS (evidence attachments, gig deliverables, dispute exhibits, etc.):
import { TrustFlowClient } from '@trustflow/sdk'; const client = new TrustFlowClient({ contractId: process.env.TRUSTFLOW_CONTRACT_ID!, ipfs: { apiKey: process.env.IPFS_API_KEY }, }); const result = await client.storage.upload(fileBuffer, { filename: 'contract.pdf' }); if (result.ok) console.log('Uploaded:', result.data.url);
upload() accepts a Buffer, Uint8Array, ArrayBuffer, Blob or a browser File directly
(e.g. from an <input type="file">); a File's name and type are forwarded as the default
filename and content type. The request is sent to apiUrl exactly as configured.
IPFSStorage can also be used standalone via new IPFSStorage(config), and points at a
web3.storage-compatible raw-body upload API by default — pass apiUrl to target a different
IPFS pinning service.
saveSession / loadSession / clearSession detect their environment per call (via
typeof localStorage), so no setup is needed in either place:
Browser: uses localStorage automatically — sessions survive page reloads.
Node / CLI / backend: falls back to an in-memory store scoped to the current process. This does not survive process restarts. If you need durability (a long-running server, a CLI invoked repeatedly), inject your own adapter:
import { configureSessionStorage } from '@trustflow/sdk'; configureSessionStorage({ get: (key) => myFileOrRedisStore.get(key), set: (key, value) => myFileOrRedisStore.set(key, value), remove: (key) => myFileOrRedisStore.delete(key), });
SSR / bundler edge cases (Next.js, Remix, etc.): typeof localStorage can be ambiguous
when server and client code share a module graph. If session calls run on the server during
SSR, they'll silently use the in-memory fallback for that request rather than throwing — which
is usually not what you want. Call configureSessionStorage() explicitly with a no-op or
server-appropriate adapter for server-rendered code paths, and only rely on the automatic
localStorage detection in code you know runs client-side.
Sessions can be scoped to an account. saveSession / loadSession / clearSession take an
optional fourth/second scope argument; client.setSession(token, { account }) /
client.getSession({ account }) / client.clearSession({ account }) pass the account id for you,
so two accounts signed in on the same origin never overwrite each other's token. Omitting the
scope keeps the legacy single-session key, so sessions written by earlier versions still load.
Sessions also carry an expiresAt, checked via isSessionExpired(). This is a best-effort,
client-side value — the backend does not currently return a token TTL (tracked in
#82), so treat it as a lower
bound, not a guarantee, and still handle a 401 from the backend even when
isSessionExpired() returns false.
A TrustFlowClient can act as many accounts. Register them on client.accounts, then switch
with client.useAccount(id) — a field write, not a re-initialisation: the Horizon server,
the Soroban RPC server, the retry budget and the IPFS helper are all shared and stay warm.
const client = new TrustFlowClient({ contractId, accounts: [ { id: 'alice', address: ALICE, label: 'Alice', roles: ['depositor'] }, { id: 'bob', address: BOB, label: 'Bob', roles: ['beneficiary'] }, ], }); client.useAccount('bob'); // switch — no reconnect await client.getAccountInfo(); // Bob's balance + sequence await client.getAccountInfo({ account: 'alice' }); // one call, active unchanged
Every account-scoped method takes an optional account, so a single call can target an account
other than the active one. Per-account state is namespaced by account id, so switching can never
leak one account's session or credentials into another's request:
client.setSession('bob-token', { account: 'bob' }); client.getSession({ account: 'alice' }); // null — a different account's session // Per-account API keys override the client-wide one; the selected account is // identified on the request. client.accounts.update('carol', { apiKey: 'carol-backend-token' }); client.getAuthHeaders({ account: 'carol' }); // Authorization: Bearer carol-backend-token // Scope a block of calls, then restore the previous selection (even on throw). await client.asAccount('alice', async () => { /* … */ }); // Persist and restore across reloads. localStorage.setItem('accounts', JSON.stringify(client.accounts.exportState())); client.accounts.importState(JSON.parse(localStorage.getItem('accounts')!));
Naming an unregistered account throws a TrustFlowError with code ACCOUNT_NOT_FOUND. With no
accounts configured the client behaves exactly as before — single account, no context, no new
errors — so existing integrations need no changes. A runnable tour is in
examples/multi-account.ts (npm run examples:multi-account).
Horizon reads, Soroban RPC calls and raw fetch helpers retry only transient failures
(network errors, timeouts, 429, 5xx, Soroban TRY_AGAIN_LATER) with capped exponential
backoff, jitter, and a Retry-After header honoured when the server sends one. 4xx, simulation
errors, node ERROR rejections and on-chain FAILED results fail fast. One retry block
configures all of them:
const client = new TrustFlowClient({ contractId, retry: { retries: 4, retryDelayMs: 500, maxRetryDelayMs: 10_000 }, });
retries counts extra attempts, so retries: 0 disables retrying. Non-idempotent requests
(POST/PATCH) are not replayed on 5xx or a transport error by default, because the server
may have processed the request before the response was lost; opt a specific call in with
{ trustflowRetry: true }. DisputeClientOptions, ProfileClientOptions, IPFSConfig,
AuthRequestOptions and getGigs(params, options) all accept the same retry block.
MultiSigEscrowClient keeps operation state in-memory per process. To coordinate signers running
in separate processes today, round-trip state through your own store with exportState() /
importState():
// Process A (initiator) const snapshot = client.exportState(operationId); // -> hand this to your own backend/queue // Process B (a signer), after fetching that snapshot from your store const imported = client.importState(snapshot); if (!imported.ok) { throw new Error(imported.error); // malformed/corrupted snapshot } client.addSignature({ operationId, signerAddress, signedXdr }); const reExported = client.exportState(operationId); // hand the updated state back to your store
importState does not trust the verified flags in a snapshot. Every imported signature is checked
again against the transaction, and any that fail are kept as unverified, so they do not count towards
the threshold.
importState overwrites any existing local operation with the same operationId — last write
wins. If two processes both mutate after diverging from the same snapshot and both re-export,
importing one after the other discards the first's signatures rather than merging them.
Serializing concurrent writes (e.g. one writer at a time through your store) is the caller's
responsibility until a native, backend-backed MultiSigStateStore lands — tracked in
#83.
TransactionPipeline, with typed PipelineResult<T> errorsMultiSigEscrowClientJurorClientclient.storage.upload() or standalone IPFSStorage429/5xx and node deferrals with capped, jittered backoff — and never replay a 4xx, a simulation error or an on-chain failure. Configured once via ClientConfig.retryuseAccount(id), target one with { account }, per-account sessions/caches/keys via client.accountsgenerateSep7Uri from @trustflow/sdk/wallet encodes prepared transaction XDR as a SEP-0007 deep link or QR text payload (see wallet API).TrustFlowEscrowClient returns SDKResult<T> (never throws) for safe, predictable error handlingTransactionPipeline orchestrates assemble → simulate → prepare → fee-bump → submit with typed PipelineResult<T> errors and built-in exponential backoff retriesTrustFlowError on failure)error.code (ASSEMBLY_ERROR, SIMULATION_ERROR, etc.) instead of message stringsEscrowBuilder.build() returns an independent copy of its params, so a builder can be reused as a template and earlier results are never mutated by later set* callsRead more in docs/ARCHITECTURE.md
React hooks for wallet, balance, and transaction state are available from the @trustflow/sdk/react subpath, kept separate from the main entrypoint so non-React (Node/CLI) consumers aren't forced to install react:
import { useWallet, useBalance, useTransaction } from '@trustflow/sdk/react';
react (^18.0.0 || ^19.0.0) is a peer dependency, required only if you import from /react. useEscrow is exported here too — it wraps the standalone createEscrow / releaseEscrow functions with loading / error state.
This entry is a client module. It uses useState, useEffect and useCallback, so dist/hooks/index.js and dist/hooks/index.mjs are emitted with a leading 'use client' directive. In the Next.js App Router that means you can import the hooks straight from a Server Component without wrapping them in your own client file:
// app/page.tsx — a Server Component, no 'use client' needed import { useWallet } from '@trustflow/sdk/react'; export default function Page() { /* ... */ }
The other entries — the root, /escrow, /wallet and /utils — are deliberately not marked as client modules, so they stay usable on the server. The directive is injected into the build output rather than written in the source, because bundlers drop module-level directives; npm run build runs scripts/inject-use-client.js after tsup to do it, and tests/use-client-directive.test.ts fails the build if it ever goes missing or spreads to a non-React entry.
The @trustflow/sdk/escrow, @trustflow/sdk/wallet, and @trustflow/sdk/utils subpaths used in the Quick Start above are declared in package.json's exports and built as their own targets, so those imports resolve against the published package as well as from source.
npm run docs to regenerate locally)The SDK is under active development. Here's what's coming:
TransactionPipeline)axios-retry)See our GitHub Issues for detailed progress tracking.
We welcome contributions! To get started:
npm installnpm testNew contributors: Start with CONTRIBUTING.md for a complete setup guide, development workflow, and conventions. It covers:
For detailed issue and PR templates, see .github/ISSUE_TEMPLATE/ and .github/PULL_REQUEST_TEMPLATE.md.
We take security seriously. This SDK builds and signs financial transactions on Stellar.
Report security issues privately via SECURITY.md — do not open public issues for vulnerabilities.
Security features:
For the full security policy, including scope, supported versions, and responsible disclosure guidelines, see SECURITY.md.
MIT License - Copyright (c) 2026 TrustFlow Protocol
See LICENSE for details.
Securing the future of work, one transaction at a time.