Vana SDK - v2.2.2
    Preparing search index...

    Interface IAtomicStore

    Interface for atomic storage operations required by the SDK's distributed components.

    Implementations of this interface MUST guarantee atomicity for all operations. These primitives are used by the SDK's internal components to coordinate distributed state in multi-instance deployments (e.g., serverless functions).

    The SDK provides a reference Redis implementation, but you can implement this interface using any storage backend that supports atomic operations:

    • PostgreSQL with advisory locks
    • DynamoDB with conditional writes
    • MongoDB with findAndModify
    • Zookeeper or etcd for coordination
    // Using the Redis reference implementation
    import { RedisAtomicStore } from './lib/redisAtomicStore';

    const atomicStore = new RedisAtomicStore({
    redis: process.env.REDIS_URL
    });

    const vana = Vana({
    privateKey: process.env.RELAYER_PRIVATE_KEY,
    atomicStore
    });
    interface IAtomicStore {
        incr(key: string): Promise<number>;
        acquireLock(key: string, ttlSeconds: number): Promise<string | null>;
        releaseLock(key: string, lockId: string): Promise<void>;
        get(key: string): Promise<string | null>;
        set(key: string, value: string): Promise<void>;
        setWithTTL?(key: string, value: string, ttlSeconds: number): Promise<void>;
        delete?(key: string): Promise<void>;
        eval?(script: string, keys: string[], args: string[]): Promise<any>;
    }
    Index

    Methods

    • Atomically increments a counter and returns the new value.

      Parameters

      • key: string

        The key to increment

      Returns Promise<number>

      The new value after incrementing

      This operation MUST be atomic. If the key doesn't exist, it should be initialized to 0 before incrementing. The operation must return the value after incrementing.

      This is used primarily for nonce assignment where atomicity is critical to prevent conflicts under concurrent load.

      // First call returns 1
      const nonce1 = await store.incr('nonce:0x123:1480');
      // Second call returns 2
      const nonce2 = await store.incr('nonce:0x123:1480');
    • Attempts to acquire a distributed lock with automatic expiration.

      Parameters

      • key: string

        The lock key

      • ttlSeconds: number

        Time-to-live in seconds (automatic expiration)

      Returns Promise<string | null>

      A unique lock ID if acquired, null if lock is already held

      This operation MUST be atomic and follow the "SET NX EX" semantics:

      • Only succeeds if the lock doesn't exist (NX - "Not eXists")
      • Automatically expires after the specified TTL (EX - "EXpire")
      • Returns a unique lock ID on success for safe release

      The lock ID should be a unique value (e.g., UUID or timestamp+random) that prevents accidental release by other processes.

      const lockId = await store.acquireLock('nonce:lock:0x123', 5);
      if (lockId) {
      try {
      // Critical section - only one process can be here
      await performCriticalOperation();
      } finally {
      await store.releaseLock('nonce:lock:0x123', lockId);
      }
      }
    • Releases a previously acquired lock.

      Parameters

      • key: string

        The lock key

      • lockId: string

        The unique ID returned by acquireLock

      Returns Promise<void>

      This operation MUST be atomic and safe. It should only succeed if the provided lockId matches the current lock value. This prevents accidental release of locks acquired by other processes.

      Implementations should use compare-and-delete semantics or a Lua script (in Redis) to ensure atomicity.

      const lockId = await store.acquireLock('lock:key', 5);
      if (lockId) {
      // ... do work ...
      await store.releaseLock('lock:key', lockId);
      }
    • Gets the value associated with a key.

      Parameters

      • key: string

        The key to retrieve

      Returns Promise<string | null>

      The stored value, or null if not found

      This is a simple read operation. It should return null if the key doesn't exist.

      const lastUsedNonce = await store.get('lastNonce:0x123:1480');
      
    • Sets a key-value pair.

      Parameters

      • key: string

        The key to set

      • value: string

        The value to store

      Returns Promise<void>

      This is a simple write operation. It should overwrite any existing value.

      await store.set('lastNonce:0x123:1480', '42');
      
    • Sets a key-value pair with expiration.

      Parameters

      • key: string

        The key to set

      • value: string

        The value to store

      • ttlSeconds: number

        Time-to-live in seconds

      Returns Promise<void>

      This operation sets a value with automatic expiration after the specified TTL. Useful for temporary state that should be automatically cleaned up.

      // Store operation state for 24 hours
      await store.setWithTTL('operation:123', JSON.stringify(state), 86400);
    • Deletes a key.

      Parameters

      • key: string

        The key to delete

      Returns Promise<void>

      This operation removes a key from storage. Should be idempotent (no error if key doesn't exist).

      await store.delete('temp:data:123');
      
    • Executes an atomic script (e.g., Lua in Redis, stored procedure in SQL).

      Parameters

      • script: string

        The script to execute

      • keys: string[]

        Array of keys the script will operate on

      • args: string[]

        Array of arguments to pass to the script

      Returns Promise<any>

      The script's return value

      This is an optional method that allows execution of atomic scripts for complex operations that require multiple steps to be atomic. Not all stores support this - simple stores may omit it.

      For Redis: executes a Lua script For PostgreSQL: executes a stored procedure or CTE For DynamoDB: might use TransactWrite

      const result = await store.eval(
      'return redis.call("INCR", KEYS[1])',
      ['counter:users'],
      []
      );