UNPKG

@coinbase/cdp-sdk

Version:

SDK for interacting with the Coinbase Developer Platform Wallet API

186 lines • 6.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SpendTracker = exports.DEFAULT_MAX_LEDGER_ENTRIES = void 0; /* * Ledger that tracks cumulative spend and backs the onApproachingLimit notifier. */ const types_js_1 = require("./types.js"); /** * Default maximum number of ledger entries kept in memory. */ exports.DEFAULT_MAX_LEDGER_ENTRIES = 10_000; /** * Default in-memory SpendStore backed by a plain array. */ class InMemorySpendStore { entries = []; /** * Returns the current entry count. * * @returns The number of entries in the store. */ async size() { return this.entries.length; } /** * Returns all entries currently held by the store. * * @returns All ledger entries. */ async load() { return this.entries; } /** * Adds a single entry to the store. * * @param entry - The ledger entry to add. */ async append(entry) { this.entries.push(entry); } /** * Drops entries older than `olderThanMs`. * * @param olderThanMs - Cutoff timestamp in milliseconds. */ async prune(olderThanMs) { let cut = 0; while (cut < this.entries.length && this.entries[cut].at < olderThanMs) { cut++; } if (cut > 0) this.entries.splice(0, cut); } /** * Removes a specific entry by its field values. * * @param entry - The ledger entry to remove. */ async removeEntry(entry) { for (let i = this.entries.length - 1; i >= 0; i--) { const e = this.entries[i]; if (e.at === entry.at && e.atomicAmount === entry.atomicAmount && e.asset === entry.asset && e.network === entry.network && e.payTo === entry.payTo) { this.entries.splice(i, 1); return; } } } } /** * Tracks spend per asset in an append-only ledger. */ class SpendTracker { store; maxLedgerEntries; warnedOnRemove = false; /** * Creates a new SpendTracker. * * @param options - Optional configuration for the tracker. */ constructor(options = {}) { this.store = options.store ?? new InMemorySpendStore(); this.maxLedgerEntries = options.maxLedgerEntries ?? exports.DEFAULT_MAX_LEDGER_ENTRIES; if (!Number.isInteger(this.maxLedgerEntries) || this.maxLedgerEntries <= 0) { throw new types_js_1.SpendControlError("configuration_invalid", `maxLedgerEntries must be a positive integer, received ${this.maxLedgerEntries}`, { input: options.maxLedgerEntries }); } } /** * Adds a spend entry to the ledger and returns it. * * @param input - The spend record to add. * @returns The recorded ledger entry. */ async record(input) { if (input.atomicAmount < 0n) { throw new types_js_1.SpendControlError("amount_unparseable", "Cannot record a negative spend", { attempted: input.atomicAmount.toString(), asset: input.asset, network: input.network, payTo: input.payTo, }); } const entry = { atomicAmount: input.atomicAmount, asset: input.asset, network: input.network, payTo: input.payTo, at: Date.now(), }; const currentSize = this.store.size ? await this.store.size() : (await this.store.load()).length; if (currentSize >= this.maxLedgerEntries) { throw new types_js_1.SpendControlError("ledger_capacity_exceeded", `SpendTracker ledger is full (maxLedgerEntries=${this.maxLedgerEntries}); rejecting new spend record to prevent under-counting.`); } await this.store.append(entry); return entry; } /** * Sum atomic amounts for the given asset, optionally restricted to entries * recorded at or after `since`. * * @param query - Query parameters specifying asset and optional time window. * @returns The total atomic amount spent. */ async total(query) { if (query.since !== undefined && this.store.prune) { await this.store.prune(query.since); } let sum = 0n; const entries = await this.store.load(); for (const entry of entries) { if (query.since !== undefined && entry.at < query.since) continue; if (entry.asset !== query.asset) continue; sum += entry.atomicAmount; } return sum; } /** * Drop entries older than `now - windowMs`. * * @param now - Current timestamp in milliseconds. * @param windowMs - Optional rolling window size in milliseconds. */ async prune(now, windowMs) { if (windowMs === undefined || windowMs <= 0) return; const cutoff = now - windowMs; if (this.store.prune) { await this.store.prune(cutoff); } } /** * Removes a previously recorded entry, undoing its contribution to the cumulative total. * * @param entry - The ledger entry to remove. */ async removeEntry(entry) { if (this.store.removeEntry) { await this.store.removeEntry(entry); return; } if (!this.warnedOnRemove) { // eslint-disable-next-line no-console console.warn(`[@coinbase/cdp-sdk/x402] SpendTracker: store does not implement removeEntry; ` + `rollback is a no-op so the cumulative ledger may over-count after a payment failure.`); this.warnedOnRemove = true; } } /** * Returns all current ledger entries as a read-only snapshot. * * @returns All current entries. */ async entries() { return this.store.load(); } } exports.SpendTracker = SpendTracker; //# sourceMappingURL=spend-tracker.js.map