Docs/SDKs & toolsv0.0.1-preview

C SDK

A C11 client library for embedding TulaDB access in native systems — with the richest connection-hardening options of any SDK.

On this page
  1. First transfer
  2. Connection options
  3. Decimal amounts
  4. Retries
  5. Token authentication
  6. Beyond the raw client

The C SDK is a C11 static library for native integrations. It’s labeled a starter honestly — the surface covers connect, transfers, two-phase, batches, balances, and a leader-aware smart client, but it is the least generated/polished of the five. One historical note: the public symbols still carry the legacy ledgerd_ prefix; they may be renamed to tuladb_ before 1.0. Like the other SDKs it is not on a package registry: the sdk download has a prebuilt static library for Linux x86_64 (built with OpenSSL TLS, with headers and a pkg-config file) and a source archive for every other platform:

tar -xzf tuladb-c-sdk-0.0.1-preview-linux-x86_64.tar.gz
SDK=$PWD/tuladb-c-sdk-0.0.1-preview-linux-x86_64
cc app.c -I"$SDK/include" "$SDK/lib/libledgerd_client.a" -lssl -lcrypto -lpthread

# any other platform: build from source
tar -xzf tuladb-c-sdk-0.0.1-preview-src.tar.gz
make -C tuladb-c-sdk-0.0.1-preview-src/c ENABLE_OPENSSL_TLS=1

First transfer

#include <stdio.h>
#include <string.h>
#include <ledgerd/ledgerd_client.h>

ledgerd_client_t client;
ledgerd_client_options_t opts;
ledgerd_transfer_result_t result;

ledgerd_client_init(&client);
ledgerd_client_options_defaults(&opts);
opts.host = "127.0.0.1";
opts.port = 7700;

if (ledgerd_client_connect(&client, &opts) != 0) {
    fprintf(stderr, "connect failed: %s\n", ledgerd_client_last_error(&client));
    return 1;
}

ledgerd_transfer_create_t t;
memset(&t, 0, sizeof(t));
t.source_id = 1;
t.ledger_id = 1;
t.currency = 840;                       /* required: 0 is declined as a currency mismatch */
t.debit_account_id = ledgerd_u128(0, 1001);
t.credit_account_id = ledgerd_u128(0, 1002);
t.amount = 100;
snprintf(t.txn_id, sizeof(t.txn_id), "c-sdk-000001");

if (ledgerd_transfer(&client, &t, &result) != 0) {
    fprintf(stderr, "transfer failed: %s (status %d)\n",
            ledgerd_client_last_error(&client), ledgerd_client_last_status(&client));
}

ledgerd_balance_t bal;
ledgerd_get_balance(&client,
                    ledgerd_account_key(1, 840, ledgerd_u128(0, 1002)), &bal);
ledgerd_client_close(&client);

Every call returns 0 on success; on failure, ledgerd_client_last_error explains why and ledgerd_client_last_status gives the engine status behind it (match on the status, not the text). A single-op write succeeds when the item is accepted (0), a duplicate (1), or an explicit reject you asked for (41); any other item status is the call’s failure, with that status. Batch calls fill a status per item instead — classify with ledgerd_status_is_accepted, ledgerd_status_is_duplicate, ledgerd_status_is_final, ledgerd_status_is_retryable, or enforce with ledgerd_require_accepted(&client, &result, allow_duplicate). Account-addressed calls take a ledgerd_account_key_t built with ledgerd_account_key(ledger_id, currency, id).

The full write surface mirrors the other SDKs: two-phase ledgerd_reserve / ledgerd_commit / ledgerd_rollback, batches, and explicit rejects via ledgerd_reject(&client, &reject, &result) with a ledgerd_reject_create_t — its reason_code is your own classification, recorded verbatim, while the record’s result_code is engine-fixed (LEDGERD_STATUS_ERR_UPSTREAM_REJECTED).

Connection options

The options struct is the most complete connection-hardening surface in any TulaDB SDK:

opts.connect_timeout_ms = 30000;    /* separate connect/read/write timeouts */

/* TLS / mTLS */
opts.tls_enabled = 1;
opts.tls_ca_file = "/run/secrets/tuladb-ca.crt";
opts.tls_cert_file = "/run/secrets/client.crt";
opts.tls_key_file = "/run/secrets/client.key";
opts.tls_server_name = "node1.internal";

ledgerd_client_options_apply_env(&opts) layers in the standard TULADB_CLIENT_TLS_* environment variables. The same plaintext rule as every SDK applies: cleartext to a non-loopback host is refused unless opts.allow_plaintext is set deliberately, and a bearer token or API key is never sent over an insecure connection unless explicitly allowed.

Decimal amounts

Next SDK release. These helpers are on the SDK’s feat/decimal-amounts branch and ship with the next SDK release. Every SDK passes the same shared conversion vectors; see Amounts.

ledgerd_currency_scales_t *sc = ledgerd_currency_scales_new(&client);
int64_t minor;
if (ledgerd_currency_scales_to_minor(sc, 124, "800.00", &minor) == 0) { /* 80000 */ }

char buf[LEDGERD_AMOUNT_TEXT_MAX];
ledgerd_amount_int_to_decimal(80000, 2, buf, sizeof buf);       /* "800.00" */
ledgerd_amount_status_t st = ledgerd_amount_parse("800.000", 2, &minor);
/* st names the reason: ledgerd_amount_status_name(st) == "too_many_decimals" */
ledgerd_currency_scales_free(sc);

The cache is thread-safe; found scales are kept for good and “not registered” is remembered for two seconds. ledgerd_amount_decimal_to_int keeps its signature but now follows the same rules.

Retries

The smart client (ledgerd_smart_client_t) retries a failed call up to ledgerd_smart_set_retry_attempts() times after the first attempt. The default is LEDGERD_SMART_DEFAULT_RETRY_ATTEMPTS (1), which ledgerd_smart_client_init applies, and 0 turns retries off.

  • Final status. Returned at once.
  • Any other server status. The client waits a backoff delay, refreshes the leader and retries.
  • Transport failure. The client refreshes the leader and retries at once the first time, then backs off.

The delay is full jitter: backed-off retry n waits a random time in [0, min(max_delay, initial × 2ⁿ)]. A retry that couldn’t begin within max_elapsed of the first attempt isn’t started. The defaults are 50 ms, 2000 ms and 15,000 ms:

ledgerd_smart_set_retry_attempts(&sc, 3);
ledgerd_smart_set_retry_backoff(&sc, /* initial_delay_ms */ 50,
                                /* max_delay_ms */ 2000, /* max_elapsed_ms */ 15000);

Batch calls that got a reply aren’t retried. Resubmit the items where ledgerd_status_is_retryable() holds, under their same identities. ledgerd_smart_create_ledger with ledger id 0 (the server assigns the id) is retried only after a retryable status. After a transport failure or an “other” status its outcome is unknown, so the error is returned.

Token authentication

The C SDK is currently the one client with in-band connection authentication in addition to mTLS:

opts.auth_type = LEDGERD_AUTH_TYPE_BEARER;   /* or LEDGERD_AUTH_TYPE_API_KEY */
opts.auth_credential = token;

If your deployment authenticates clients with OIDC bearer tokens or API keys rather than client certificates, the C SDK (and tulactl) can present them today; the other SDKs authenticate with mTLS client certificates. See Security.

Beyond the raw client

  • ledgerd_transfer_batch — batched submission with per-item results.
  • ledgerd_buffered_new(client, batch_size, flush_interval_ms) — transparent write coalescing.
  • ledgerd_smart_client_t — leader-aware client: leader discovery, retry across elections, smart reads (ledgerd_smart_get_history, ledgerd_smart_list_*).
  • ledgerd_get_balance_min_seq(client, key, min_batch_seq, &out) — a balance read gated on a sequence number, for read-your-writes against any node.
  • ledgerd_list_account_balances(client, ledger_id, id, ...) — every currency balance one id holds in a ledger.
  • ledgerd_derive_transfer_id(source, ledger, currency, txn_id, out) — recompute a transfer’s identity offline.