@koralabs/kora-labs-common 6.9.4 → 6.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/aws/jwtSigner.d.ts +0 -2
  2. package/aws/kmsEnvironment.d.ts +0 -8
  3. package/aws/kmsEnvironment.js +4 -4
  4. package/aws/objectStore.d.ts +0 -8
  5. package/chain/cache/ShortTermCache.d.ts +17 -0
  6. package/chain/cache/ShortTermCache.js +46 -0
  7. package/chain/config.d.ts +9 -0
  8. package/chain/config.js +40 -0
  9. package/chain/datum/imageDatum.d.ts +14 -0
  10. package/chain/datum/imageDatum.js +87 -0
  11. package/chain/failover/ChainProviderFailover.d.ts +50 -0
  12. package/chain/failover/ChainProviderFailover.js +91 -0
  13. package/chain/failover/interfaces.d.ts +62 -0
  14. package/chain/failover/interfaces.js +2 -0
  15. package/chain/index.d.ts +9 -0
  16. package/chain/index.js +28 -0
  17. package/chain/providerConfig.d.ts +22 -0
  18. package/chain/providerConfig.js +13 -0
  19. package/chain/providers/Blockfrost.d.ts +59 -0
  20. package/chain/providers/Blockfrost.js +142 -0
  21. package/chain/providers/Koios.d.ts +68 -0
  22. package/chain/providers/Koios.js +214 -0
  23. package/chain/transport/fetchProviderJson.d.ts +26 -0
  24. package/chain/transport/fetchProviderJson.js +165 -0
  25. package/environment/index.js +4 -4
  26. package/fn/index.d.ts +0 -2
  27. package/fn/index.js +1 -1
  28. package/handles/api.js +0 -1
  29. package/handles/constants.js +1 -1
  30. package/handles/interfaces/ScriptDetails.js +1 -1
  31. package/handles/interfaces/api.js +3 -3
  32. package/handles/interfaces/index.d.ts +4 -4
  33. package/handles/interfaces/index.js +5 -5
  34. package/handles/policies.js +1 -1
  35. package/handles/utils.d.ts +1 -1
  36. package/logger/index.js +1 -1
  37. package/mpt/index.js +17 -7
  38. package/mpt/labelSet.d.ts +0 -2
  39. package/package.json +7 -7
  40. package/types/index.js +4 -4
  41. package/types/profile-header.js +2 -2
  42. package/utils/cbor/index.d.ts +4 -6
  43. package/utils/cbor/index.js +18 -8
  44. package/utils/crypto/index.d.ts +2 -4
  45. package/utils/crypto/index.js +23 -13
  46. package/utils/index.d.ts +5 -5
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  /** True when a local RSA key is configured (i.e. signing/verifying needs no AWS). */
4
2
  export declare const isLocalJwtSigner: () => boolean;
5
3
  /** Sign `message` with RS256 — local private key if present, else AWS KMS. */
@@ -1,11 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- /// <reference types="node" />
4
- /// <reference types="node" />
5
- /// <reference types="node" />
6
- /// <reference types="node" />
7
- /// <reference types="node" />
8
- /// <reference types="node" />
9
1
  import { KMSClient } from '@aws-sdk/client-kms';
10
2
  export type KmsClientLike = Pick<KMSClient, 'send'>;
11
3
  /**
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadAfterHydratingKmsEnvironment = exports.hydrateKmsEnvironment = exports.decryptKmsCiphertext = exports.isKmsDisabled = void 0;
3
+ exports.isKmsDisabled = void 0;
4
+ exports.decryptKmsCiphertext = decryptKmsCiphertext;
5
+ exports.hydrateKmsEnvironment = hydrateKmsEnvironment;
6
+ exports.loadAfterHydratingKmsEnvironment = loadAfterHydratingKmsEnvironment;
4
7
  const client_kms_1 = require("@aws-sdk/client-kms");
5
8
  const KMS_ENV_BUNDLE_KEY = 'KMS_ENV_BUNDLE_ENC';
6
9
  const KMS_DISABLED_KEY = 'KORA_KMS_DISABLED';
@@ -21,7 +24,6 @@ async function decryptKmsCiphertext(ciphertext, client = new client_kms_1.KMSCli
21
24
  }
22
25
  return Buffer.from(response.Plaintext).toString('utf8');
23
26
  }
24
- exports.decryptKmsCiphertext = decryptKmsCiphertext;
25
27
  async function hydrateKmsEnvironment({ env = process.env, client, keys } = {}) {
26
28
  // Self-host: SOPS/age decrypts the env at deploy time, so the values are already
27
29
  // plaintext and there is no KMS to call. Skip decryption entirely (don't even
@@ -83,9 +85,7 @@ async function hydrateKmsEnvironment({ env = process.env, client, keys } = {}) {
83
85
  }
84
86
  return hydratedKeys;
85
87
  }
86
- exports.hydrateKmsEnvironment = hydrateKmsEnvironment;
87
88
  async function loadAfterHydratingKmsEnvironment(load, options) {
88
89
  await hydrateKmsEnvironment(options);
89
90
  return load();
90
91
  }
91
- exports.loadAfterHydratingKmsEnvironment = loadAfterHydratingKmsEnvironment;
@@ -1,11 +1,3 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- /// <reference types="node" />
4
- /// <reference types="node" />
5
- /// <reference types="node" />
6
- /// <reference types="node" />
7
- /// <reference types="node" />
8
- /// <reference types="node" />
9
1
  export interface ObjectStoreConfig {
10
2
  region: string;
11
3
  endpoint?: string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Process-local short-TTL cache with in-flight de-duplication. Concurrent identical requests share
3
+ * a single upstream fetch, and a successful result is served from memory for `ttlMs`. Failures are
4
+ * NEVER cached (the fetcher promise only writes to the store on resolve). The clock is injectable so
5
+ * TTL behavior is unit-testable. Ported from the handle.me BFF into kora-labs-common.
6
+ */
7
+ export declare class ShortTermCache {
8
+ private readonly now;
9
+ private store;
10
+ private inflight;
11
+ constructor(now?: () => number);
12
+ get<T>(key: string, ttlMs: number, fetcher: () => Promise<T>): Promise<T>;
13
+ delete(key: string): void;
14
+ clear(): void;
15
+ }
16
+ /** Shared process-local cache instance for repetitive chain reads. */
17
+ export declare const sharedShortTermCache: ShortTermCache;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sharedShortTermCache = exports.ShortTermCache = void 0;
4
+ /**
5
+ * Process-local short-TTL cache with in-flight de-duplication. Concurrent identical requests share
6
+ * a single upstream fetch, and a successful result is served from memory for `ttlMs`. Failures are
7
+ * NEVER cached (the fetcher promise only writes to the store on resolve). The clock is injectable so
8
+ * TTL behavior is unit-testable. Ported from the handle.me BFF into kora-labs-common.
9
+ */
10
+ class ShortTermCache {
11
+ constructor(now = Date.now) {
12
+ this.now = now;
13
+ this.store = new Map();
14
+ this.inflight = new Map();
15
+ }
16
+ async get(key, ttlMs, fetcher) {
17
+ const hit = this.store.get(key);
18
+ if (hit && hit.expiresAt > this.now()) {
19
+ return hit.value;
20
+ }
21
+ const pending = this.inflight.get(key);
22
+ if (pending) {
23
+ return pending;
24
+ }
25
+ const promise = (async () => {
26
+ const value = await fetcher();
27
+ this.store.set(key, { value, expiresAt: this.now() + ttlMs });
28
+ return value;
29
+ })().finally(() => {
30
+ this.inflight.delete(key);
31
+ });
32
+ this.inflight.set(key, promise);
33
+ return promise;
34
+ }
35
+ delete(key) {
36
+ this.store.delete(key);
37
+ this.inflight.delete(key);
38
+ }
39
+ clear() {
40
+ this.store.clear();
41
+ this.inflight.clear();
42
+ }
43
+ }
44
+ exports.ShortTermCache = ShortTermCache;
45
+ /** Shared process-local cache instance for repetitive chain reads. */
46
+ exports.sharedShortTermCache = new ShortTermCache();
@@ -0,0 +1,9 @@
1
+ import { ChainProvider } from './failover/interfaces';
2
+ import { ChainProviderConfig } from './providerConfig';
3
+ export * from './providerConfig';
4
+ /** Read a ChainProviderConfig from the standard environment variables. */
5
+ export declare const chainConfigFromEnv: (env?: NodeJS.ProcessEnv) => ChainProviderConfig;
6
+ /**
7
+ * Factory for the default read-failover provider set: Koios + Blockfrost, mutually redundant.
8
+ */
9
+ export declare const buildChainProviders: (config: ChainProviderConfig) => ChainProvider[];
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.buildChainProviders = exports.chainConfigFromEnv = void 0;
18
+ const providerConfig_1 = require("./providerConfig");
19
+ const Koios_1 = require("./providers/Koios");
20
+ const Blockfrost_1 = require("./providers/Blockfrost");
21
+ __exportStar(require("./providerConfig"), exports);
22
+ /** Read a ChainProviderConfig from the standard environment variables. */
23
+ const chainConfigFromEnv = (env = process.env) => {
24
+ var _a, _b, _c, _d;
25
+ return ({
26
+ network: (0, providerConfig_1.normalizeNetwork)((_a = env.NETWORK) !== null && _a !== void 0 ? _a : 'mainnet'),
27
+ koiosBearerToken: ((_b = env.KOIOS_API_BEARER_TOKEN) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
28
+ blockfrostApiKey: ((_c = env.BLOCKFROST_API_KEY) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
29
+ apiHost: ((_d = env.HANDLE_API_ENDPOINT) === null || _d === void 0 ? void 0 : _d.trim()) || undefined
30
+ });
31
+ };
32
+ exports.chainConfigFromEnv = chainConfigFromEnv;
33
+ /**
34
+ * Factory for the default read-failover provider set: Koios + Blockfrost, mutually redundant.
35
+ */
36
+ const buildChainProviders = (config) => [
37
+ new Koios_1.Koios(config),
38
+ new Blockfrost_1.Blockfrost(config)
39
+ ];
40
+ exports.buildChainProviders = buildChainProviders;
@@ -0,0 +1,14 @@
1
+ import { ICreatorDefaults } from '../../handles/interfaces';
2
+ export declare const decodeDatumViaApi: <T>(apiHost: string, datum: string, schema?: Record<string, unknown>, fetcher?: (url: string, init?: any) => Promise<{
3
+ text: () => Promise<string>;
4
+ }>) => Promise<T>;
5
+ export declare const decode0xHexToUtf8: (v: unknown) => unknown;
6
+ export declare const normalizeCreatorDefaultTextFields: (creatorDefaults: Record<string, unknown>) => void;
7
+ export interface ImageDatumDetails {
8
+ image: string;
9
+ creatorDefaults?: ICreatorDefaults;
10
+ metadata?: Record<string, unknown>;
11
+ }
12
+ export declare const getImageDataFromDatum: (apiHost: string, datum: string, fetcher?: (url: string, init?: any) => Promise<{
13
+ text: () => Promise<string>;
14
+ }>) => Promise<ImageDatumDetails>;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getImageDataFromDatum = exports.normalizeCreatorDefaultTextFields = exports.decode0xHexToUtf8 = exports.decodeDatumViaApi = void 0;
4
+ // Schema-guided datum decoding for background/CIP-68 image details. Ported verbatim (behavior-
5
+ // preserving) from the handle.me BFF: it POSTs the datum CBOR to the Handles API `/datum` decoder
6
+ // with a field schema so specific TEXT fields are UTF-8-decoded. This is handle-property-correctness
7
+ // critical — the schema and the 0x-hex fallback below must not drift.
8
+ const defaultFetch = (url, init) => globalThis.fetch(url, init);
9
+ const decodeDatumViaApi = async (apiHost, datum, schema, fetcher = defaultFetch) => {
10
+ const response = await fetcher(`${apiHost}/datum?from=plutus_data_cbor&to=json`, {
11
+ method: 'POST',
12
+ headers: { 'Content-Type': 'application/json' },
13
+ body: JSON.stringify({ cbor: datum, schema })
14
+ });
15
+ const text = await response.text();
16
+ if (!text.trim()) {
17
+ // Empty response — return empty object instead of crashing.
18
+ return {};
19
+ }
20
+ return JSON.parse(text);
21
+ };
22
+ exports.decodeDatumViaApi = decodeDatumViaApi;
23
+ // The api /datum decoder only UTF-8-decodes fields the schema can path to. When a background's
24
+ // CIP-68 `extra` is Constr-wrapped (Constr(0,[map]) at [2]), the schema's `[2]` fields don't match
25
+ // and these TEXT fields come back as `0x<hex>`. Colours/arrays are legitimately hex and left alone;
26
+ // only the known free-text fields are normalized back to UTF-8. A value already decoded (no 0x
27
+ // prefix) passes through unchanged.
28
+ const CREATOR_DEFAULT_TEXT_FIELDS = [
29
+ 'font',
30
+ 'qr_dot',
31
+ 'qr_inner_eye',
32
+ 'qr_outer_eye',
33
+ 'text_ribbon_gradient',
34
+ 'qr_image'
35
+ ];
36
+ const decode0xHexToUtf8 = (v) => {
37
+ if (typeof v === 'string' && /^0x[0-9a-fA-F]*$/.test(v) && v.length > 2) {
38
+ try {
39
+ return Buffer.from(v.slice(2), 'hex').toString('utf8');
40
+ }
41
+ catch (_a) {
42
+ return v;
43
+ }
44
+ }
45
+ return v;
46
+ };
47
+ exports.decode0xHexToUtf8 = decode0xHexToUtf8;
48
+ const normalizeCreatorDefaultTextFields = (creatorDefaults) => {
49
+ for (const field of CREATOR_DEFAULT_TEXT_FIELDS) {
50
+ if (field in creatorDefaults) {
51
+ creatorDefaults[field] = (0, exports.decode0xHexToUtf8)(creatorDefaults[field]);
52
+ }
53
+ }
54
+ };
55
+ exports.normalizeCreatorDefaultTextFields = normalizeCreatorDefaultTextFields;
56
+ const getImageDataFromDatum = async (apiHost, datum, fetcher) => {
57
+ var _a, _b, _c, _d, _e, _f, _g, _h;
58
+ let decodedDatum;
59
+ try {
60
+ decodedDatum = await (0, exports.decodeDatumViaApi)(apiHost, datum, {
61
+ constructor_0: {
62
+ '[0]': { name: 'string', image: 'string', category: 'string' },
63
+ '[2]': {
64
+ font: 'string',
65
+ text_ribbon_gradient: 'string',
66
+ force_creator_settings: 'bool',
67
+ qr_inner_eye: 'string',
68
+ qr_outer_eye: 'string',
69
+ qr_dot: 'string',
70
+ qr_image: 'string'
71
+ }
72
+ }
73
+ }, fetcher);
74
+ }
75
+ catch (_j) {
76
+ return { image: '' };
77
+ }
78
+ const image = (_b = (_a = decodedDatum === null || decodedDatum === void 0 ? void 0 : decodedDatum.constructor_0) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.image;
79
+ let data = { image };
80
+ const creatorDefaults = (_f = (_e = (_d = (_c = decodedDatum === null || decodedDatum === void 0 ? void 0 : decodedDatum.constructor_0) === null || _c === void 0 ? void 0 : _c[2]) === null || _d === void 0 ? void 0 : _d.constructor_0) === null || _e === void 0 ? void 0 : _e[0]) !== null && _f !== void 0 ? _f : (_g = decodedDatum.constructor_0) === null || _g === void 0 ? void 0 : _g[2];
81
+ if (creatorDefaults) {
82
+ (0, exports.normalizeCreatorDefaultTextFields)(creatorDefaults);
83
+ data = { image, creatorDefaults, metadata: (_h = decodedDatum === null || decodedDatum === void 0 ? void 0 : decodedDatum.constructor_0) === null || _h === void 0 ? void 0 : _h[0] };
84
+ }
85
+ return data;
86
+ };
87
+ exports.getImageDataFromDatum = getImageDataFromDatum;
@@ -0,0 +1,50 @@
1
+ import { AccountAsset, AddressInfo, BackgroundImageDetails, ChainProvider, ChainProviderAsset, ChainProviderUtxo } from './interfaces';
2
+ export interface FailoverOptions {
3
+ /**
4
+ * Cooldown (ms) during which a provider that just failed is skipped as the *start* provider,
5
+ * so a throttled/down provider doesn't add latency to every subsequent call. It is still used
6
+ * as a fallback if the others also fail. Set 0 to disable (pure random start = legacy behavior).
7
+ */
8
+ unhealthyCooldownMs?: number;
9
+ now?: () => number;
10
+ /** Injectable RNG in [0,1) for deterministic start selection in tests. Defaults to Math.random. */
11
+ random?: () => number;
12
+ }
13
+ /**
14
+ * Health-aware failover across interchangeable ChainProviders. Reads start on a healthy provider
15
+ * (random among healthy, else random overall) and fall through to the next on error — giving mutual
16
+ * Koios/Blockfrost redundancy from a single call site. Ported from the handle.me BFF and generalized
17
+ * (no app-level constants) so every repo shares it via kora-labs-common.
18
+ */
19
+ export declare class ChainProviderFailover {
20
+ private providers;
21
+ private unhealthyUntil;
22
+ private readonly cooldownMs;
23
+ private readonly now;
24
+ private readonly random;
25
+ constructor(providers: ChainProvider[], options?: FailoverOptions);
26
+ private isHealthy;
27
+ private getStartIndex;
28
+ private getNextProviderIndex;
29
+ private getResponse;
30
+ getLatestTransactionForAsset(policyId: string, hex: string): Promise<string | null>;
31
+ getLatestBlock(): Promise<{
32
+ height: number;
33
+ slot: number;
34
+ time: number;
35
+ }>;
36
+ getAssets(policyId: string): Promise<{
37
+ policyId: string;
38
+ hex: string;
39
+ }[]>;
40
+ getAssetDatum(policyId: string, hex: string): Promise<string | null>;
41
+ getCip25AssetImage(policyId: string, hex: string): Promise<string | null>;
42
+ getRefScriptCbor(tx: string, policyId: string, hex: string): Promise<string>;
43
+ getTxUtxos(tx: string): Promise<ChainProviderUtxo>;
44
+ getAssetUtxo(policyId: string, hex: string): Promise<ChainProviderAsset | null>;
45
+ getAssetsByStakeKey(stakeKey: string): Promise<AccountAsset[]>;
46
+ getDatumFromHash(datumHash: string): Promise<string | null>;
47
+ getBackgroundImageDetails(policyId: string, hex: string): Promise<BackgroundImageDetails>;
48
+ getAddressInfo(bech32Address: string): Promise<AddressInfo>;
49
+ getAddressUTxOs(bech32Address: string): Promise<ChainProviderAsset[]>;
50
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChainProviderFailover = void 0;
4
+ /**
5
+ * Health-aware failover across interchangeable ChainProviders. Reads start on a healthy provider
6
+ * (random among healthy, else random overall) and fall through to the next on error — giving mutual
7
+ * Koios/Blockfrost redundancy from a single call site. Ported from the handle.me BFF and generalized
8
+ * (no app-level constants) so every repo shares it via kora-labs-common.
9
+ */
10
+ class ChainProviderFailover {
11
+ constructor(providers, options = {}) {
12
+ var _a, _b, _c;
13
+ this.providers = [];
14
+ this.unhealthyUntil = [];
15
+ this.providers = providers;
16
+ this.unhealthyUntil = providers.map(() => 0);
17
+ this.cooldownMs = (_a = options.unhealthyCooldownMs) !== null && _a !== void 0 ? _a : 30000;
18
+ this.now = (_b = options.now) !== null && _b !== void 0 ? _b : Date.now;
19
+ this.random = (_c = options.random) !== null && _c !== void 0 ? _c : Math.random;
20
+ }
21
+ isHealthy(index) {
22
+ return this.unhealthyUntil[index] <= this.now();
23
+ }
24
+ getStartIndex() {
25
+ const healthy = this.providers.map((_, i) => i).filter((i) => this.isHealthy(i));
26
+ const pool = healthy.length > 0 ? healthy : this.providers.map((_, i) => i);
27
+ return pool[Math.floor(this.random() * pool.length)];
28
+ }
29
+ getNextProviderIndex(currentIndex) {
30
+ return (currentIndex + 1) % this.providers.length;
31
+ }
32
+ async getResponse(funcName, args, currentIndex, runNum) {
33
+ const num = runNum !== null && runNum !== void 0 ? runNum : 0;
34
+ const index = currentIndex !== null && currentIndex !== void 0 ? currentIndex : this.getStartIndex();
35
+ const chainProvider = this.providers[index];
36
+ return await chainProvider[funcName](...args).catch((error) => {
37
+ // Mark the failed provider unhealthy for the cooldown window so it isn't chosen as the
38
+ // start provider again immediately; it remains a valid fallback.
39
+ if (this.cooldownMs > 0)
40
+ this.unhealthyUntil[index] = this.now() + this.cooldownMs;
41
+ if (num === this.providers.length - 1)
42
+ throw error;
43
+ return this.getResponse(funcName, args, this.getNextProviderIndex(index), num + 1);
44
+ });
45
+ }
46
+ async getLatestTransactionForAsset(policyId, hex) {
47
+ return this.getResponse('getLatestTransactionForAsset', [policyId, hex]);
48
+ }
49
+ async getLatestBlock() {
50
+ return this.getResponse('getLatestBlock', []);
51
+ }
52
+ async getAssets(policyId) {
53
+ return this.getResponse('getAssets', [policyId]);
54
+ }
55
+ async getAssetDatum(policyId, hex) {
56
+ return this.getResponse('getAssetDatum', [policyId, hex]);
57
+ }
58
+ async getCip25AssetImage(policyId, hex) {
59
+ return this.getResponse('getCip25AssetImage', [policyId, hex]);
60
+ }
61
+ async getRefScriptCbor(tx, policyId, hex) {
62
+ return this.getResponse('getRefScriptCbor', [tx, policyId, hex]);
63
+ }
64
+ async getTxUtxos(tx) {
65
+ return this.getResponse('getTxUtxos', [tx]);
66
+ }
67
+ async getAssetUtxo(policyId, hex) {
68
+ // Asset resolution is correctness-critical (backs handle-resolution chain fallback): a
69
+ // provider lagging a burn returns a stale UTxO and falsely resurrects a de-indexed handle.
70
+ // Prefer Blockfrost (reads the latest asset tx live; Koios's tx index can lag a burn by
71
+ // minutes). Koios stays the failover on error.
72
+ const preferredIndex = this.providers.findIndex((p) => { var _a; return ((_a = p.name) !== null && _a !== void 0 ? _a : '').toLowerCase().includes('blockfrost'); });
73
+ return this.getResponse('getAssetUtxo', [policyId, hex], preferredIndex >= 0 ? preferredIndex : undefined);
74
+ }
75
+ async getAssetsByStakeKey(stakeKey) {
76
+ return this.getResponse('getAssetsByStakeKey', [stakeKey]);
77
+ }
78
+ async getDatumFromHash(datumHash) {
79
+ return this.getResponse('getDatumFromHash', [datumHash]);
80
+ }
81
+ async getBackgroundImageDetails(policyId, hex) {
82
+ return this.getResponse('getBackgroundImageDetails', [policyId, hex]);
83
+ }
84
+ async getAddressInfo(bech32Address) {
85
+ return this.getResponse('getAddressInfo', [bech32Address]);
86
+ }
87
+ async getAddressUTxOs(bech32Address) {
88
+ return this.getResponse('getAddressUTxOs', [bech32Address]);
89
+ }
90
+ }
91
+ exports.ChainProviderFailover = ChainProviderFailover;
@@ -0,0 +1,62 @@
1
+ import { ICreatorDefaults } from '../../handles/interfaces';
2
+ export interface ChainProviderAsset {
3
+ address: string;
4
+ amount: {
5
+ unit: string;
6
+ quantity: string;
7
+ }[];
8
+ inline_datum: string | null;
9
+ output_index: number;
10
+ tx_hash: string;
11
+ data_hash: string | null;
12
+ reference_script_hash?: string | null;
13
+ reference_script_cbor?: string | null;
14
+ }
15
+ export interface ChainProviderUtxo {
16
+ outputs: ChainProviderAsset[];
17
+ }
18
+ export interface AccountAsset {
19
+ asset: string;
20
+ amount: string;
21
+ }
22
+ export interface AddressInfo {
23
+ address: string;
24
+ amount: {
25
+ unit: string;
26
+ quantity: string;
27
+ }[];
28
+ stake_address?: string | null;
29
+ script: boolean;
30
+ }
31
+ export interface BackgroundImageDetails {
32
+ image: string;
33
+ creatorDefaults?: ICreatorDefaults;
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+ /**
37
+ * The unified read contract every chain provider (Koios, Blockfrost, …) implements. The failover
38
+ * orchestrator only depends on this interface, so providers are hot-swappable and mutually redundant.
39
+ */
40
+ export interface ChainProvider {
41
+ name: string;
42
+ getLatestTransactionForAsset(policyId: string, hex: string): Promise<string | null>;
43
+ getLatestBlock(): Promise<{
44
+ height: number;
45
+ slot: number;
46
+ time: number;
47
+ }>;
48
+ getAssets(policyId: string): Promise<{
49
+ policyId: string;
50
+ hex: string;
51
+ }[]>;
52
+ getAssetDatum(policyId: string, hex: string): Promise<string | null>;
53
+ getCip25AssetImage(policyId: string, hex: string): Promise<string | null>;
54
+ getRefScriptCbor(tx: string, policyId: string, hex: string): Promise<string>;
55
+ getTxUtxos(tx: string): Promise<ChainProviderUtxo>;
56
+ getAssetUtxo(policyId: string, hex: string): Promise<ChainProviderAsset | null>;
57
+ getAssetsByStakeKey(stakeKey: string): Promise<AccountAsset[]>;
58
+ getDatumFromHash(datumHash: string): Promise<string | null>;
59
+ getBackgroundImageDetails(policyId: string, hex: string): Promise<BackgroundImageDetails>;
60
+ getAddressInfo(bech32Address: string): Promise<AddressInfo>;
61
+ getAddressUTxOs(bech32Address: string): Promise<ChainProviderAsset[]>;
62
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ export * from './transport/fetchProviderJson';
2
+ export * from './failover/interfaces';
3
+ export * from './failover/ChainProviderFailover';
4
+ export * from './cache/ShortTermCache';
5
+ export * from './providerConfig';
6
+ export * from './config';
7
+ export * from './datum/imageDatum';
8
+ export * from './providers/Koios';
9
+ export * from './providers/Blockfrost';
package/chain/index.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ // Subpath-only module: `@koralabs/kora-labs-common/chain`.
18
+ // NOT re-exported from the root index — this is a server-side chain-access layer and pulls Node-only
19
+ // deps. Keeping it off the root barrel preserves the isomorphic root (same rule the `aws` module follows).
20
+ __exportStar(require("./transport/fetchProviderJson"), exports);
21
+ __exportStar(require("./failover/interfaces"), exports);
22
+ __exportStar(require("./failover/ChainProviderFailover"), exports);
23
+ __exportStar(require("./cache/ShortTermCache"), exports);
24
+ __exportStar(require("./providerConfig"), exports);
25
+ __exportStar(require("./config"), exports);
26
+ __exportStar(require("./datum/imageDatum"), exports);
27
+ __exportStar(require("./providers/Koios"), exports);
28
+ __exportStar(require("./providers/Blockfrost"), exports);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Configuration for the concrete chain providers. Injected explicitly (no reading of app-level
3
+ * constants) so every consumer wires its own env once and the module stays app-agnostic.
4
+ */
5
+ export interface ChainProviderConfig {
6
+ /** 'mainnet' | 'preview' | 'preprod' — selects Koios/Blockfrost hosts. */
7
+ network: string;
8
+ /** Koios bearer token (optional; anonymous tier works without it). */
9
+ koiosBearerToken?: string;
10
+ /** Blockfrost project id for the target network. */
11
+ blockfrostApiKey?: string;
12
+ /**
13
+ * Base URL of the Handles API used for schema-guided datum decoding (POST /datum). Defaults to
14
+ * the public api.handle.me host for the network. Preserves the exact datum-decode behavior the
15
+ * BFF relied on (schema-pathed UTF-8 field decoding), which is handle-property-correctness
16
+ * critical — do NOT swap it for a local CBOR decode without matching that behavior.
17
+ */
18
+ apiHost?: string;
19
+ }
20
+ export declare const normalizeNetwork: (network: string) => string;
21
+ /** Public api.handle.me host for a network (mainnet has no subdomain prefix). */
22
+ export declare const defaultApiHost: (network: string) => string;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ // Leaf config module (no imports from providers) so the concrete providers and the factory can both
3
+ // depend on it without a cycle.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.defaultApiHost = exports.normalizeNetwork = void 0;
6
+ const normalizeNetwork = (network) => (network || 'mainnet').toLowerCase();
7
+ exports.normalizeNetwork = normalizeNetwork;
8
+ /** Public api.handle.me host for a network (mainnet has no subdomain prefix). */
9
+ const defaultApiHost = (network) => {
10
+ const net = (0, exports.normalizeNetwork)(network);
11
+ return net === 'mainnet' ? 'https://api.handle.me' : `https://${net}.api.handle.me`;
12
+ };
13
+ exports.defaultApiHost = defaultApiHost;
@@ -0,0 +1,59 @@
1
+ import { ICreatorDefaults } from '../../handles/interfaces';
2
+ import { AccountAsset, AddressInfo, ChainProvider, ChainProviderAsset, ChainProviderUtxo } from '../failover/interfaces';
3
+ import { ChainProviderConfig } from '../providerConfig';
4
+ export interface DrepMetadataValue {
5
+ '@value': string;
6
+ }
7
+ export interface DrepReference {
8
+ '@type': string;
9
+ label: string | DrepMetadataValue;
10
+ uri: string | DrepMetadataValue;
11
+ }
12
+ interface DrepMetadata {
13
+ drep_id: string;
14
+ json_metadata: {
15
+ body: {
16
+ paymentAddress: string | DrepMetadataValue;
17
+ givenName: string | DrepMetadataValue;
18
+ image: {
19
+ contentUrl: string | DrepMetadataValue;
20
+ };
21
+ objectives: string | DrepMetadataValue;
22
+ motivations: string | DrepMetadataValue;
23
+ qualifications: string | DrepMetadataValue;
24
+ references: DrepReference[];
25
+ };
26
+ };
27
+ }
28
+ export declare class Blockfrost implements ChainProvider {
29
+ private readonly config;
30
+ name: string;
31
+ constructor(config: ChainProviderConfig);
32
+ private fetchBlockfrost;
33
+ getDrepMetadata(drepId: string): Promise<DrepMetadata>;
34
+ getLatestBlock(): Promise<{
35
+ height: number;
36
+ slot: number;
37
+ time: number;
38
+ }>;
39
+ getLatestTransactionForAsset(policyId: string, hex: string): Promise<string | null>;
40
+ getAssets(policyId: string): Promise<{
41
+ policyId: string;
42
+ hex: string;
43
+ }[]>;
44
+ getAssetUtxo(policyId: string, hex: string): Promise<ChainProviderAsset | null>;
45
+ getDatumFromHash(datumHash: string): Promise<string | null>;
46
+ getAssetDatum(policyId: string, hex: string): Promise<string | null>;
47
+ getCip25AssetImage(policyId: string, hex: string): Promise<string | null>;
48
+ getRefScriptCbor(tx: string, policyId: string, hex: string): Promise<string>;
49
+ getTxUtxos(tx: string): Promise<ChainProviderUtxo>;
50
+ getAssetsByStakeKey(stakeKey: string): Promise<AccountAsset[]>;
51
+ getBackgroundImageDetails(policyId: string, hex: string): Promise<{
52
+ image: string;
53
+ creatorDefaults?: ICreatorDefaults;
54
+ metadata?: Record<string, unknown>;
55
+ }>;
56
+ getAddressInfo(bech32Address: string): Promise<AddressInfo>;
57
+ getAddressUTxOs(bech32Address: string): Promise<ChainProviderAsset[]>;
58
+ }
59
+ export {};