Typed TypeScript definitions and client for calling EarnProof Soroban contracts without shell-outs to Stellar CLI. All parameters and return types are validated at compile time.
npm install @stellar/stellar-sdkCopy .env.example to .env and populate contract IDs, network passphrase, and RPC URL:
cp .env.example .env
# Edit .env with your deployment detailsFrom scripts/deployment-manifest.testnet.json:
{
"contracts": {
"protocolConfig": "CC3OREX5QBIKJ5JOW36JFJJW7TLAKJOVT5WJXEITGALO7MU32KHICS2A",
"issuerRegistry": "CB73TVWVJIIVNTKLWSHZB5NL2UIF3B3EUL4YH4MUD6EYX6SFIHE77D2F",
"proofRegistry": "CCMTAXBWN2ZGEDVKGHT6GQENZSTBSLQAGYGGKJWNMDSTVRT2QNMMNWRK"
}
}import { EarnProofClient } from './artifacts/bindings';
const client = new EarnProofClient({
protocolConfigId: process.env.PROTOCOL_CONFIG_ID,
issuerRegistryId: process.env.ISSUER_REGISTRY_ID,
proofRegistryId: process.env.PROOF_REGISTRY_ID,
networkPassphrase: process.env.NETWORK_PASSPHRASE,
rpcUrl: process.env.SOROBAN_RPC_URL,
secretKey: process.env.SIGNER_SECRET_KEY,
});
// Read contract state (no signing required)
const isProtocolPaused = await client.isPaused({});
console.log('Protocol paused:', isProtocolPaused);
// Register a proof (requires issuer authorization)
const proofIdHash = '0x' + sha256(proofId);
const commitmentHash = '0x' + sha256(payload);
await client.registerProof({
proof_id_hash: proofIdHash,
commitment_hash: commitmentHash,
issuer_address: issuerStellarAddress,
schema_version: 1,
expires_at: BigInt(futureTimestamp),
});
// Query proof state
const proof = await client.getProof({ proof_id_hash: proofIdHash });
console.log('Proof status:', proof.status);const isValid = await client.isValidProof({
proof_id_hash: proofIdHash,
});const issuer = await client.getIssuer({
issuer_id_hash: issuerIdHash,
});
console.log('Issuer status:', issuer.status); // 'Active' | 'Suspended' | 'Revoked'await client.approveSchemaVersion({
version: 1,
});await client.suspendIssuer({
issuer_id_hash: issuerIdHash,
});import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { EarnProofClient } from './artifacts/bindings';
@Injectable()
export class ContractService {
private client: EarnProofClient;
constructor(configService: ConfigService) {
this.client = new EarnProofClient({
protocolConfigId: configService.getOrThrow('PROTOCOL_CONFIG_ID'),
issuerRegistryId: configService.getOrThrow('ISSUER_REGISTRY_ID'),
proofRegistryId: configService.getOrThrow('PROOF_REGISTRY_ID'),
networkPassphrase: configService.getOrThrow('NETWORK_PASSPHRASE'),
rpcUrl: configService.getOrThrow('SOROBAN_RPC_URL'),
secretKey: configService.getOrThrow('SIGNER_SECRET_KEY'),
});
}
async verifyProof(proofIdHash: string): Promise<boolean> {
return this.client.isValidProof({ proof_id_hash: proofIdHash });
}
async registerProof(params: RegisterProofParams) {
return this.client.registerProof(params);
}
}import { ContractInvocationError } from './artifacts/bindings';
try {
await client.registerProof(params);
} catch (err) {
if (err instanceof ContractInvocationError) {
console.error(`Failed to invoke ${err.method} on ${err.contractId}`);
console.error(`Reason: ${err.message}`);
}
}All methods are fully typed:
// ✅ TypeScript knows what parameters registerProof accepts
await client.registerProof({
proof_id_hash: '0x...',
commitment_hash: '0x...',
issuer_address: 'G...',
schema_version: 1,
expires_at: 1234567890n,
});
// ❌ TypeScript error: missing parameter
await client.registerProof({
proof_id_hash: '0x...',
// commitment_hash is required!
});
// ❌ TypeScript error: wrong type
await client.registerProof({
// ...
schema_version: '1', // must be number, not string
});Important: Hash all identifiers before passing to contracts.
import crypto from 'crypto';
function sha256(value: string): string {
return crypto.createHash('sha256').update(value, 'utf8').digest('hex');
}
const proofIdHash = '0x' + sha256(proofId);
const issuerIdHash = '0x' + sha256(issuerId);
const commitmentHash = '0x' + sha256(credentialPayload);
const metadataHash = '0x' + sha256(publicMetadata);initializeProtocolConfig— Initialize (admin)getAdminProtocolConfig— Get admin addresssetAdmin— Change admin (admin)isPaused— Check if pausedpause— Pause operations (admin)unpause— Resume operations (admin)approveSchemaVersion— Approve schema (admin)deprecateSchemaVersion— Deprecate schema (admin)isSchemaVersionApproved— Check if approvedgetConfigVersion— Get config version counter
initializeIssuerRegistry— Initialize (admin)getAdminIssuerRegistry— Get admin addressregisterIssuer— Register issuer (admin)updateIssuer— Update metadata (admin)suspendIssuer— Suspend issuer (admin)reactivateIssuer— Reactivate issuer (admin)revokeIssuer— Revoke issuer (admin)rotateIssuerAddress— Rotate address (admin)getIssuer— Get issuer by IDisActiveIssuer— Check if activeisActiveAddress— Check if active by addressgetIssuerByAddress— Get issuer by address
initializeProofRegistry— Initialize (admin)registerProof— Register proof (issuer)revokeProof— Revoke proof (issuer)adminRevokeProof— Admin revoke (admin)getProof— Get proof by IDisValidProof— Check if validisRevoked— Check if revokedgetAdminProofRegistry— Get admin addressgetIssuerRegistry— Get issuer registry contract addressgetProtocolConfig— Get protocol config contract address
See docs/bindings-integration.md for complete guide including:
- Advanced usage patterns
- Testing strategies
- Security best practices
- Deployment guides
After contract changes:
./scripts/generate-bindings.ps1 -Network testnet
git add artifacts/bindings/
git commit -m "chore: regenerate bindings"Ensure contract addresses match format: C followed by 55 alphanumeric characters.
Secret key must start with S and be a valid Stellar key.
Use exact passphrase from network config:
- Testnet:
"Test SDF Network ; September 2015" - Mainnet:
"Public Global Stellar Network ; September 2015"
All parameters must match exact types. Use 0x prefix for hex hashes, use BigInt(...) for timestamps.
- Integration guide:
docs/bindings-integration.md - Contract reference:
docs/backend-integration.md - Storage model:
docs/storage-model.md