Typed TypeScript helpers for the Bear Protocol's two Soroban contracts
(agent_identity, agentic_commerce), plus x402 payment middleware and a
self-paying marcFetch wrapper.
- Node.js ≥ 20 (ESM-only package)
npm install marc-stellar-sdkNote: The package ships ESM only (
"type": "module"). CommonJS is not supported. Ensure your project'stsconfig.json/package.jsontargets ESM (e.g."module": "NodeNext").
import { IdentityClient, CommerceClient, marcFetch, TESTNET } from "marc-stellar-sdk";
import { Keypair } from "@stellar/stellar-sdk";
const keypair = Keypair.fromSecret("S...");
// ── Identity ──────────────────────────────────────────────────────────────────
const identity = new IdentityClient(TESTNET);
// Register an agent on-chain
const agentId = await identity.register(keypair, "https://ipfs.io/ipfs/<cid>/metadata.json");
// Resolve an agent by Stellar address
const agent = await identity.agentOf(keypair.publicKey());
// ── Commerce (escrow) ─────────────────────────────────────────────────────────
const commerce = new CommerceClient(TESTNET);
// Client creates a funded job
const jobId = await commerce.createJob(
keypair,
providerAddress,
evaluatorAddress,
TESTNET.usdcToken,
10_000_000n, // 10 USDC (7 decimals)
"Analyse Q3 sales data and return a CSV summary",
);
// Provider submits deliverable
await commerce.submit(providerKeypair, jobId, "https://ipfs.io/ipfs/<deliverable-cid>");
// Evaluator approves → funds released 99 % provider / 1 % treasury
await commerce.complete(evaluatorKeypair, jobId);
// ── High-frequency micropayments (off-chain vouchers) ───────────────────────
const channel = await commerce.openChannel(
payerKeypair,
providerAddress,
10_000_000n,
channelVoucherKeypair,
);
let latestVoucher = channel.createVoucher(1_000n);
for (let request = 1; request < 100; request += 1) {
latestVoucher = channel.createVoucher(1_000n); // signed locally; no RPC call
}
await commerce.closeChannel(channel.channelId, latestVoucher, providerKeypair);
// After 24 hours, either participant calls forceClose(participant, channelId).
// ── Paywall middleware (Express) ──────────────────────────────────────────────
import express from "express";
import { marcPaywall } from "marc-stellar-sdk";
const app = express();
app.use(
"/api/summarize",
marcPaywall({
payee: keypair.publicKey(),
price: 1_000_000, // 1 USDC
network: "testnet",
}),
);
// ── Auto-paying fetch ─────────────────────────────────────────────────────────
const fetch402 = marcFetch({
signer: keypair,
network: "testnet",
// Custom headers forwarded on every request (e.g. API keys, auth tokens)
headers: { "X-Api-Key": "my-key" },
onPayment: (status) => console.log("payment:", status),
});
const res = await fetch402("https://agent.example/api/summarize", {
method: "POST",
body: JSON.stringify({ text: "..." }),
});Frontend apps can use the SDK without ever exposing a private key. The browser
entry point (marc-stellar-sdk/browser) signs every transaction through the
Freighter extension, which prompts the user for
approval.
import { BrowserIdentityClient, connectFreighter } from "marc-stellar-sdk/browser";
import { TESTNET } from "marc-stellar-sdk";
// 1. Connect the wallet (throws FreighterNotInstalledError if absent)
const { publicKey } = await connectFreighter();
// 2. Use the browser clients — Freighter popup appears for each signature
const identity = new BrowserIdentityClient(TESTNET);
const agentId = await identity.register(publicKey, "https://ipfs.io/ipfs/<cid>/metadata.json");
const commerce = new BrowserCommerceClient(TESTNET);
await commerce.createJob(publicKey, provider, evaluator, TESTNET.usdcToken, 10_000_000n, "...");Returns { publicKey } for the connected account. Detects a network mismatch
between Freighter and the target network and throws a descriptive error.
Implements the same Signer interface as Keypair, so it can be passed to any
SDK method that accepts a signer. It wraps @stellar/freighter-api and never
reads or stores a secret key.
Thrown when the Freighter extension is not detected. Its message includes an
install link (https://www.freighter.app/) so apps can surface a helpful prompt.
| Method | Description |
|---|---|
register(keypair, uri) |
Register a new agent; returns agentId: bigint |
getAgent(agentId) |
Fetch an Agent record by numeric ID |
agentOf(address) |
Look up the Agent registered to a Stellar address |
updateUri(keypair, agentId, newUri) |
Update the metadata URI |
updateOwner(keypair, agentId, newOwnerKeypair) |
Transfer the agent to a new wallet; requires signatures from both the current owner and newOwnerKeypair |
deregister(keypair, agentId) |
Remove the agent registration |
| Method | Description |
|---|---|
createJob(keypair, provider, evaluator, token, budget, description) |
Create + fund an escrow job |
submit(keypair, jobId, deliverable) |
Provider submits work |
complete(keypair, jobId) |
Evaluator approves; triggers payout |
cancel(keypair, jobId) |
Client cancels and recovers budget |
createJobMultiEval(client, provider, evaluators, threshold, ...) |
Fund a job requiring M-of-N evaluator approvals |
approveJob(evaluator, jobId) |
Record approval; pays out at quorum |
openChannel(payer, provider, deposit, voucherKeypair, token?) |
Open a one-way escrowed payment channel |
closeChannel(channelId, voucher, provider) |
Submit or challenge with a signed cumulative voucher |
forceClose(participant, channelId) |
Settle after the 24-hour challenge period |
getJob(jobId) |
Fetch a Job record |
feeBps() |
Read the current protocol fee in basis points |
| Option | Type | Description |
|---|---|---|
payee |
string |
Stellar address that receives payment |
price |
number |
Amount in smallest token units |
network |
"testnet" | "pubnet" |
Network (default: "testnet") |
token |
string |
Token contract address (defaults to testnet USDC) |
| Option | Type | Description |
|---|---|---|
signer |
Keypair |
Keypair used to sign payment transactions |
network |
"testnet" | "pubnet" |
Network (default: "testnet") |
rpcUrl |
string? |
Custom Soroban RPC URL |
headers |
Record<string, string>? |
Custom HTTP headers forwarded on every request (e.g. API keys, auth tokens) |
onPayment |
(status: PaymentStatus) => void |
Payment lifecycle callback |
timeoutMs |
number? |
Per-request timeout in milliseconds |
maxPaymentAttempts |
number? |
Max 402-retry attempts (default: 1) |
fetchImpl |
typeof fetch? |
Custom fetch implementation (tests / adapters) |
import { TESTNET, MAINNET } from "marc-stellar-sdk";Both presets expose rpcUrl, networkPassphrase, identityContract,
commerceContract, and usdcToken. Contract addresses can be overridden via
environment variables:
| Variable | Description |
|---|---|
MARC_TESTNET_IDENTITY_CONTRACT |
Override testnet identity contract |
MARC_TESTNET_COMMERCE_CONTRACT |
Override testnet commerce contract |
MARC_TESTNET_USDC_TOKEN |
Override testnet USDC token |
STELLAR_RPC_URL |
Override RPC endpoint (both networks) |
import type {
Agent,
Job,
JobStatus,
MarcConfig,
MarcFetchOptions,
PaymentStatus,
CommerceEventName,
} from "marc-stellar-sdk";Providers can validate collected signatures with verifyChannelVoucher(voucher, voucherPublicKey). Vouchers are cumulative, domain-separated commitments;
store the highest-value valid voucher and submit it during channel closing.
Use a dedicated voucher keypair and keep its secret private; it is distinct
from the wallet that funds the on-chain channel.
The prepublishOnly script runs clean + build before every publish.