Read-client that fetches a 0–100 risk score for a Stellar address or asset from an on-chain risk oracle, and exposes it to the Gryd Lock extension behind a stable interface.
grydlock-oracle-adapter is the closest thing Gryd Lock has to a backend — but it runs no server. It is a small, read-only client: given a destination, it calls a Soroban smart contract, reads a score, and returns it. Nothing more.
Status:
StubOracleis implemented and returns scores from the vendoredgrydlock-testkitfixtures. A live oracle connection is not yet wired.
Gryd Lock needs to warn users about risky Stellar addresses and assets before they sign a transaction, but it should not be in the business of computing that risk itself. Embedding scoring logic directly in the extension would mean:
- The extension would need direct chain access and scoring logic baked into its own codebase
- Swapping or upgrading the scoring engine would require an extension release
- There would be no way to develop or test the extension's warning flow without a live oracle
At a high level, it does one thing, deliberately narrowly scoped:
- 🔎 Reads — takes a destination (Stellar address or asset) and calls the on-chain risk oracle's
get_score()function via Soroban - 🔌 Adapts — normalizes the oracle response behind a single, stable
RiskOracleinterface so the scoring backend can be swapped without touching the extension - 📤 Exposes — returns a plain 0–100 score to the Gryd Lock extension, with no chain-specific types leaking across the boundary
Full API reference is generated from the source JSDoc with TypeDoc and
published to GitHub Pages on every push to main:
📖 gryd-lock.github.io/grydlock-oracle-adapter
To build the reference locally:
npm run docs # generate the HTML reference into docs/
npm run docs:check # validate JSDoc coverage without emitting files (used in CI)docs:check fails if any exported symbol is missing a doc comment, so the published reference
stays complete as the public surface grows.
RiskOracleinterface — one method,getScore(destination), that both implementations satisfyStubOracle— lookup-table score source backed by vendoredgrydlock-testkitfixtures, for local development and thegrydlock-testkitevaluation; no network callsProvenanceOracle— wraps anyRiskOracleand emits a structured provenance record (source, timestamp, cache status, latency) for every score, via an injectableLoggerLoggerinterface — minimal structured logging seam (debug/info/warn/error) with a no-op default; the library never writes to the console on its ownSorobanOracle(planned) — callsget_score()on the live on-chain risk oracle contract and returns the result- Fallback (planned) — a slow or unreachable oracle degrades gracefully instead of stalling the signing flow
graph TB
subgraph Extension["Gryd Lock Extension"]
UI[Signing Flow UI]
end
subgraph Adapter["grydlock-oracle-adapter"]
IFACE[RiskOracle interface]
STUB[StubOracle]
SOROBAN[SorobanOracle - planned]
end
subgraph Chain["Stellar Network"]
CONTRACT[On-chain Risk Oracle Contract]
end
UI -->|getScore destination| IFACE
IFACE --> STUB
IFACE -.->|not yet wired| SOROBAN
SOROBAN -.->|get_score| CONTRACT
| Component | Role | Status |
|---|---|---|
src/RiskOracle.ts |
Defines the getScore(destination) contract and the ScoredResult metadata types |
Implemented |
src/StubOracle.ts |
Lookup-table score source, backed by vendored grydlock-testkit fixtures |
Implemented, tested |
src/ProvenanceOracle.ts |
Decorator that logs a structured provenance record for every score | Implemented, tested |
src/Logger.ts |
Injectable structured Logger interface with a no-op default |
Implemented |
src/SorobanOracle.ts |
Live client against the on-chain oracle contract | Not started |
src/fixtures/testkit/ is a vendored, point-in-time copy of grydlock-testkit's
destinations.json and scores.json — not a live sync. If the testkit fixtures change, re-copy
them here, then run npm run generate:fixtures to refresh their raw-text companion modules (see
below), to pick up the update.
Because these files are manually copied rather than pulled in as a dependency, a bad copy —
truncated file, wrong schema version, non-numeric score — would otherwise pass TypeScript's
structural typing silently and only surface as a confusing runtime failure. To catch that at the
source, src/fixtures/testkit/schema.ts validates the shape of both files, and
src/fixtures/testkit/index.ts runs that validation once, when the module is first imported
(not on every StubOracle.getScore() call). A malformed fixture throws a FixtureValidationError
naming the offending file, field, and — since validation happens during parsing rather than after
it — the entry's exact position in the source file:
FixtureValidationError: Invalid vendored fixture "scores.json": score for "GABC..." must be a finite number, got string ("10") (at line 4, column 32, offset 118)
Loading is incremental, not materialize-then-validate. scores.ts/destinations.ts don't
import the .json files directly (which would hand a bundler's JSON loader — or JSON.parse — the
whole file to parse into a JS object graph before validation could even start). Instead:
scripts/generate-fixture-text.mjsemitsscores.text.ts/destinations.text.ts, each just the vendored JSON file's exact source characters embedded as a string constant (export default "...";). That's a plain string literal any bundler or the browser loads like any other module — no Node-onlyfs/streaming API ships in the bundle, and no special bundler loader config is required downstream.tests/fixtureText.sync.test.tsfails the build if a generated file drifts from its source.json.src/fixtures/testkit/jsonScanner.tsis a small hand-rolled, token-at-a-time JSON tokenizer (JsonScanner) over that raw text, tracking line/column/offset as it scans.schema.ts'sparseScoresFixtureIncremental/parseDestinationsFixtureIncrementaluse it to validate each entry the moment it's tokenized, so a malformed entry throws — with its source position — before any later entry in the file is even scanned. (validateScoresFixture/validateDestinationsFixture, validating an already-parsed value, remain for callers that have one on hand — e.g. tests.)
tests/benchmarks/fixtureStreaming.budget.test.ts enforces this with hard budgets at 50,000
synthetic destinations (an order of magnitude past the real fixture's size): import-to-first-lookup
latency stays under 600ms, and — the check that specifically rules out a "parse fully but budget
generously" shortcut — the incremental parser's peak retained heap stays under a small constant
multiple of a deliberately-materialize-twice baseline built solely for that comparison, verified
structurally (via a JSON.parse call-count spy) rather than by absolute numbers alone.
StubOracle and the test suite both import the validated scores / destinations exports from
src/fixtures/testkit/index.ts rather than reading the JSON files directly, so any re-copy of the
testkit fixtures is checked before it can reach either.
The adapter exposes one job: turn a destination into a score.
// illustrative — not yet implemented
interface RiskOracle {
// Returns a risk score 0–100 for a Stellar address or asset.
getScore(destination: string): Promise<number>;
}The extension depends on this shape and nothing beneath it. Two implementations are planned:
- StubOracle — returns a score from the vendored
grydlock-testkitfixture lookup table (falling back to a default for unrecognized destinations). Used for development and for thegrydlock-testkitevaluation. No network. - SorobanOracle — calls
get_score()on the live on-chain risk oracle contract and returns the result. Wired in a later phase.
Every destination is validated and canonicalized before any score lookup, so a malformed or
forged identifier surfaces as an InvalidDestinationError rather than silently scoring as the
default. The grammar is:
| Shape | Example | Decision |
|---|---|---|
G… ed25519 account |
GCRRYBV5… |
Accepted as-is |
M… muxed account |
MCRRYBV5… |
Accepted, scored as its base G account |
C… contract address |
CADQOBYH… |
Accepted as-is |
L… liquidity pool |
LAEQSCIJ… |
Accepted as-is |
<code>:<issuer> asset |
SCAM:GAJLLIIP… |
Accepted; SEP-11 code + G issuer |
S… T… X… P… B… |
— | Rejected: signer/transaction constructs |
| anything else | not-a-stellar-address |
Rejected |
A muxed address is the same underlying ed25519 account with a routing tag attached, so its on-chain risk is the base account's risk — rejecting it would leave a user paying a custodial exchange deposit address with no warning at all. The 64-bit subaccount id is still decoded and exposed on the validation result for future use.
src/StrKeyCodec.ts implements base32, the CRC16-XModem checksum and version-byte/payload-length
checks from the strkey specification rather than delegating to @stellar/stellar-sdk. The SDK's
StrKey appears in exactly one file — tests/StrKeyCodec.differential.test.ts — where it is the
ground-truth oracle for a 5,000+ input differential fuzz suite that asserts identical
accept/reject verdicts and identical decoded payload bytes.
Alongside the bare-number contract, the interface file defines an opt-in metadata shape
(issue #17's design) so richer sources — fallback chains, caches — can report how a score
was produced without breaking getScore consumers:
interface ScoredResult {
score: number; // 0–100
timestamp: number; // epoch ms when the score was produced
source: OracleSource; // which oracle/tier answered, e.g. "StubOracle", "soroban"
cacheStatus: CacheStatus; // "live" | "cache-fresh" | "cache-stale" | "default" | "unknown"
confidence?: number;
}
interface DetailedRiskOracle extends RiskOracle {
getScoreDetailed(destination: string): Promise<ScoredResult>;
}ProvenanceOracle is a decorator that wraps any RiskOracle and emits one structured
provenance record per call through an injected Logger — callers keep calling
getScore(dest) exactly as before:
const oracle = new ProvenanceOracle(new StubOracle(), { logger: myLogger });
const score = await oracle.getScore(dest); // same number as before
// myLogger.info('score_provenance', {
// event: 'score_provenance',
// destination: 'G...',
// score: 95,
// source: 'StubOracle',
// cacheStatus: 'unknown',
// timestamp: 1752762896000,
// latencyMs: 1,
// outcome: 'success',
// })If the wrapped oracle implements getScoreDetailed, its reported source and cache status
flow into the record; otherwise the record uses the wrapper's configured source label and an
"unknown" cache status. Failed calls are rethrown unchanged and logged at error level
with outcome: 'error'.
The provenance log is the recommended way to answer "why did the user see this score."
Wire a real Logger into ProvenanceOracle (routing to the extension's own logging), then
filter for score_provenance entries for the destination in question. Each entry tells you:
source— which oracle/tier actually answered (live contract, cache, stub, default)cacheStatus— whether the value was live, fresh-from-cache, stale, or a fallback defaulttimestamp/latencyMs— when the call completed and how long it tookoutcome/error— whether the underlying source failed and why
A disputed Critical-tier warning that traces to source: "StubOracle" or
cacheStatus: "cache-stale" is a very different bug than one backed by a live on-chain read —
the provenance record makes that distinction visible after the fact.
// illustrative
const oracle = new StubOracle(); // swap for SorobanOracle later
const score = await oracle.getScore(dest); // 0–100
showWarning(score); // extension maps score → tiergrydlock-oracle-adapter/
│
├── README.md ← This file
├── package.json ← Package manifest and npm scripts
├── tsconfig.json ← TypeScript compiler config (strict mode)
├── eslint.config.mjs ← ESLint flat config
├── .prettierrc.json ← Prettier config
├── vitest.config.ts ← Vitest config
├── commitlint.config.js ← Conventional-commits lint rules
├── stryker.config.json ← Mutation testing config (src/ tree)
│
├── .husky/commit-msg ← Local commit-msg hook, runs commitlint
├── .github/workflows/ci.yml ← CI: typecheck, lint, format check, test, build, bundle size, commitlint
│
├── scripts/
│ ├── bundle-size.mjs ← esbuild-based bundle-size budget + tree-shaking check
│ └── generate-fixture-text.mjs ← Emits *.text.ts raw-text modules from the vendored *.json
│
├── src/
│ ├── RiskOracle.ts ← Interface definition + ScoredResult metadata types
│ ├── StubOracle.ts ← Lookup-table implementation, backed by fixtures/
│ ├── StrKeyCodec.ts ← From-scratch Stellar strkey codec (base32 + CRC16-XModem)
│ ├── DestinationValidator.ts ← Destination grammar: G/M/C/L addresses + SEP-11 assets
│ ├── ProvenanceOracle.ts ← Decorator emitting a provenance record per score
│ ├── Logger.ts ← Injectable structured Logger interface, no-op default
│ ├── SorobanOracle.ts ← Live oracle client (planned, not yet in src/)
│ ├── fixtures/testkit/
│ │ ├── destinations.json ← Vendored grydlock-testkit fixture (labelled destinations)
│ │ ├── scores.json ← Vendored grydlock-testkit fixture (destination -> score)
│ │ ├── destinations.text.ts ← Generated: destinations.json's raw text as a string constant
│ │ ├── scores.text.ts ← Generated: scores.json's raw text as a string constant
│ │ ├── jsonScanner.ts ← Hand-rolled incremental JSON tokenizer + position tracking
│ │ ├── schema.ts ← Runtime shape validation (object-based + incremental) for both files
│ │ └── index.ts ← Validates + exports the fixtures once, at module load
│ └── index.ts ← Barrel export
│
└── tests/
├── StubOracle.test.ts ← getScore range + label-ordering tests against the fixtures
├── StrKeyCodec.test.ts ← base32 / CRC16-XModem / version-byte unit tests
├── StrKeyCodec.differential.test.ts ← 5k-input differential fuzz against the SDK's StrKey
├── DestinationValidator.test.ts ← destination grammar + SEP-11 asset-code boundary tests
├── fixtureSchema.incremental.test.ts ← Incremental parser parity + position assertions
├── fixtureText.sync.test.ts ← Fails if a generated *.text.ts drifts from its source .json
├── benchmarks/fixtureStreaming.budget.test.ts ← Enforced latency/memory budgets at 50k entries
└── ProvenanceOracle.test.ts ← provenance record shape, pass-through, and error-path tests
npm install
npm run build # compile src/ to dist/
npm test # run the test suite
npm run typecheck # tsc --noEmit
npm run lint # eslint .
npm run format # prettier --write .
npm run size # bundle-size budget + tree-shaking checkimport { CoalescingOracle, Logger, StubOracle } from './src';
// Optional: structured logger injection (no-op by default).
const logger: Logger = {
debug: (message, meta) => console.debug(message, meta),
info: (message, meta) => console.info(message, meta),
warn: (message, meta) => console.warn(message, meta),
error: (message, meta) => console.error(message, meta),
};
const oracle = new CoalescingOracle(new StubOracle(logger), logger);
const score = await oracle.getScore('GAJLLIIPHII6OCG4KQJIGPCHVN6DNCRBXHX6DEUTPE7MQ6OONAYBRLET'); // 95, labelled "malicious" in grydlock-testkit- TypeScript
- Soroban SDK — reading the on-chain score
- Stellar SDK (JS) — address / asset handling
- Stellar Testnet — all development
npm testCovers:
StubOracle.getScorereturns a number within 0–100 for every destination in the vendoredgrydlock-testkitfixtures, and a default score for unrecognized destinations- Fixture destinations labelled
maliciousscore higher than those labelledclean ProvenanceOraclepasses scores through unchanged, emits one structured provenance record pergetScore/getScoreDetailedcall (source, timestamp, cache status, latency), reflects metadata fromDetailedRiskOracleinners, and logs anerroroutcome when the wrapped oracle throws
Because this package ships inside a browser extension (grydlock-extension), its footprint
directly affects extension load time and web-store review. CI enforces both a size budget and
tree-shaking behavior on every PR:
npm run sizeThe check (scripts/bundle-size.mjs) bundles the package with esbuild (minified ESM, from the
TypeScript source — the same consumption path the extension's bundler will use once the ESM
build output from #37 lands) for representative import patterns:
| Import pattern | Current size (minified) | Budget |
|---|---|---|
import { StubOracle } only |
~0.8 KB (~0.6 KB gzip) | 5 KB |
Full barrel (export * from ..) |
~0.8 KB (~0.6 KB gzip) | 10 KB |
Two things fail CI:
- Budget regression — a pattern's minified size exceeds its budget. If the growth is
intentional (a real feature), raise the budget in
scripts/bundle-size.mjsin the same PR and call it out in the PR description. - Tree-shaking leak — the
StubOracle-only pattern bundles any module outside its explicit allowlist (StubOracle, theRiskOracletypes, and the score fixtures). This guarantees that importing onlyStubOraclenever drags inSorobanOracle, aggregation, or other future code; when new modules are added to the barrel, they must be tree-shakeable (no module-level side effects) or the check fails.
For extension-side contributors: the "StubOracle only" row is the integration cost of the current recommended usage — under 1 KB gzipped added to the extension bundle.
Line/branch coverage only shows whether code executed during a test run, not whether the test
actually asserted on the result. Stryker Mutator is configured
(stryker.config.json) to measure that: it makes small deliberate changes ("mutants") to
src/**/*.ts — e.g. flipping ?? to &&, deleting a return value — and reruns the test suite
against each one. A mutant that still passes the suite ("survived") marks a gap: the code path
ran, but nothing would have caught it breaking.
npm run test:mutationBaseline (established alongside this tooling, src/StubOracle.ts at the time): Stryker found
2 mutants in the only file with executable logic (RiskOracle.ts is a type-only interface and
index.ts is a barrel export — TypeScript erases the former and there's nothing to mutate in the
latter). Of those 2:
- 1 killed — the
??→&&mutation ingetScoreis caught by the existing "malicious scores higher than clean scores" test. - 1 compile error — deleting the function body trips
tsc's "must return a value" check before the mutant ever reaches a test run. This is caught by thetypescriptchecker, not a test; it's effectively an equivalent mutant given the codebase'sstrictTypeScript config, and isn't counted against the mutation score. - 0 survived.
Mutation score: 100% (1/1 of the mutants Stryker could actually run against).
Threshold policy: informational-only for now — thresholds.break is null, so a low score
won't fail CI, and mutation testing runs nightly via
.github/workflows/mutation.yml (also triggerable manually)
rather than gating every PR, since a full mutation run is slower than the rest of the CI pipeline.
Revisit this once SorobanOracle and the resilience features land and there's a meaningfully
larger surface — at that point, consider setting thresholds.break and/or moving the check into
the main ci.yml pipeline.
- Define the
RiskOracleinterface and shipStubOracle - Back
StubOraclewith vendoredgrydlock-testkitfixtures instead of a hardcoded table - Wire
StubOracleinto the extension and confirm the query path end to end on testnet - Implement
SorobanOracleagainst a live oracle contract on testnet - Add caching and a timeout / fallback so a slow or unreachable oracle degrades gracefully instead of stalling the signing flow
- For the extension — never talks to the chain directly; it just asks the adapter for a score
- For the scoring backend — pluggable; swap the oracle and nothing upstream changes
- For development — the signing-flow UI can be built and tested against
StubOraclewith no live backend at all
- TypeScript ^6.0.3, Vitest ^4.1.10, ESLint ^10.6.0 + typescript-eslint ^8.63.0, Prettier ^3.9.4 — see
package.jsonfor the full, pinned list @stellar/stellar-sdk— currently only used bytests/StrKeyCodec.differential.test.tsas a reference oracle to fuzz-testsrc/StrKeyCodec.tsagainst; not imported anywhere insrc/(destination validation is a from-scratch, dependency-free reimplementation — see that file's doc comment). Will become load-bearing onceSorobanOracleis implemented.package.json'soverrides.axiospinsaxiosto^1.18.1:@stellar/stellar-sdk(every release from 15.0.1 through the current 16.0.1) pins an exact, olderaxiosversion that falls in several since-patched advisories' vulnerable ranges (all fixed inaxios@1.18.0). Since nothing insrc/calls into the SDK's HTTP layer yet, overriding carries no runtime risk today; remove this override once the SDK bumps its ownaxiospin upstream.
MIT
grydlock-oracle-adapter is being developed as an open-source contribution to the Stellar ecosystem. We are actively looking for collaborators with experience in:
- Stellar / Soroban smart contract development (Rust)
- TypeScript backend and browser-extension development
- On-chain data analysis and Stellar Horizon API integration
- Testing and evaluation methodology (
grydlock-testkit)
Quick checklist for contributions:
- All tests pass:
npm test - Code follows project style guidelines:
npm run lintandnpm run format:check - New features include tests
- Documentation is updated
- Commit messages follow the Conventional Commits style below
Commit messages are linted with commitlint using the
@commitlint/config-conventional
preset, so that commit history stays parseable for automated semantic versioning. Every commit
message must follow the Conventional Commits format:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Common <type> values:
| Type | Use for |
|---|---|
feat |
A new feature |
fix |
A bug fix |
docs |
Documentation-only changes |
style |
Formatting changes with no code meaning change (e.g. Prettier) |
refactor |
A code change that neither fixes a bug nor adds a feature |
test |
Adding or correcting tests |
chore |
Tooling, dependency, or build-process changes |
Examples:
feat: add SorobanOracle implementation
fix(RiskOracle): handle missing destination score
docs: update README license section to MIT
chore: add commitlint and husky commit-msg hook
This is enforced two ways:
- Locally — a husky
commit-msghook runscommitlinton every commit. Runnpm installonce after cloning so husky installs the hook (via thepreparescript). - In CI — the
commitlintjob in.github/workflows/ci.ymllints every commit on a pull request, covering contributors who bypass the local hook (e.g.git commit --no-verify).
Versioning and CHANGELOG.md are automated with
semantic-release, driven entirely by the Conventional
Commits history above — there is no manual version bump.
- Trigger:
.github/workflows/release.ymlruns semantic-release on every push tomain. - Version bump: derived from commit types since the last release —
fix:→ patch,feat:→ minor, aBREAKING CHANGE:footer → major. Commits that don't map to a bump (docs,chore,style, etc.) don't trigger a release. - Output: a Git tag, an updated
CHANGELOG.md, and a GitHub Release with generated notes. Thepackage.jsonversionfield is updated and committed back tomainby the@semantic-release/gitplugin (commit messagechore(release): <version> [skip ci], which intentionally skips re-triggering CI/release). - npm publishing: not enabled. This package is
"private": trueand not intended for npm consumption today, so the@semantic-release/npmplugin is deliberately omitted from.releaserc.json. Releases are tags + changelog + GitHub Release only. If the package needs to be published to npm later, that's a separate decision — add the@semantic-release/npmplugin and flipprivatetofalseat that point. - Tooling install: semantic-release and its plugins are resolved by
npxat release time rather than added todevDependencies/package-lock.json, so this workflow doesn't require a lockfile change to adopt.
Gryd Lock is split across four repos in the Gryd-lock GitHub org:
| Repo | Role | Has code? |
|---|---|---|
grydlock-research |
Design study: threat model, system design, warning-tier thresholds, evaluation methodology. The reasoning the other three repos implement. | No — design docs only |
grydlock-extension |
Browser extension. Intercepts a wallet's signing flow (Freighter first), decodes the pending transaction, asks the oracle adapter for a score, and shows a tiered warning. | Yes — early build: Freighter intercept, XDR decode, and warning popup implemented |
grydlock-oracle-adapter (this repo) |
Read-only client. Exposes RiskOracle.getScore(destination) to the extension; backed by StubOracle today, SorobanOracle later. |
Yes — RiskOracle + StubOracle implemented and tested |
grydlock-testkit |
Testnet fixtures and stub scores used to evaluate the extension + adapter together. | Yes — labelled destinations, stub scores, and sample XDRs implemented, with a fixture validator in CI |
graph LR
SIGN[Wallet signing flow] --> EXT[grydlock-extension\nintercept + decode]
EXT -->|getScore destination| ADAPTER[grydlock-oracle-adapter\nRiskOracle]
ADAPTER -->|StubOracle today| SCORE[0-100 score]
ADAPTER -.->|SorobanOracle later| CHAIN[On-chain risk oracle]
SCORE --> EXT
EXT --> TIER[Warning tier]
TIER --> USER{User decision}
grydlock-testkit supplies the fixture destinations and expected scores that grydlock-extension
and grydlock-oracle-adapter are evaluated against. grydlock-research is upstream of all
three — it defines the threat model and the warning-tier thresholds below.
1. RiskOracle interface — defined here at src/RiskOracle.ts:
interface RiskOracle {
getScore(destination: string): Promise<number>; // 0-100
}grydlock-extension depends on this shape only — it does not know whether the score came from
StubOracle or a live oracle. If this signature changes, grydlock-extension needs a matching
update.
2. Warning tiers — defined in grydlock-research, consumed by grydlock-extension to decide
how loudly to warn:
| Score | Tier | Behaviour |
|---|---|---|
| 0–20 | Low | Proceed |
| 21–50 | Elevated | Soft warning |
| 51–75 | High | Strong warning, require confirm |
| 76–100 | Critical | Recommend abort |
Don't verify the shared contracts above by reading READMEs across repos — that's exactly the manual process that lets drift slip through. The canonical way to check sync is:
npm run sync:checkscripts/check-cross-repo-sync.mjs fetches the current state of the contracts from the other
public repos and diffs them against this repo:
- Warning tiers — parses the canonical table in
grydlock-research's README and compares it against bothgrydlock-extension's implementation (src/lib/tiers.ts) and this repo's own README table RiskOracle.getScore— compares the signature insrc/RiskOracle.tsagainst the stand-in the extension declares insrc/adapter/oracleAdapter.ts
On drift it prints a PASS/FAIL report with the expected (canonical) vs actual values, writes
drift-report.md, and exits non-zero. CI runs it weekly (Mondays 06:00 UTC, plus on PRs that
touch a contract surface — see .github/workflows/cross-repo-sync.yml), since drift
originates in the other repos rather than in pushes here; a scheduled run that finds drift
automatically opens (or updates) a tracking issue labelled cross-repo-drift. A fetch or
parse failure exits with a distinct code (2) and is reported as "needs a human look", not as
confirmed drift.
- Treat this section as the source of truth for cross-repo context. Each repo's own README covers repo-local conventions.
- Before assuming a name/function/interface still exists in another repo, verify it there — this
reflects each repo's state as of the last time it was checked, not a live feed. For the two
shared contracts above, run
npm run sync:checkinstead of eyeballing. - If a change here affects
RiskOracleor the warning-tier thresholds, call it out so the corresponding repo can be updated.
For issues and questions:
- GitHub Issues: https://github.com/Gryd-lock/grydlock-oracle-adapter/issues
- Stellar Discord: https://discord.gg/stellar
grydlock-oracle-adapter — the one door Gryd Lock knocks on for a risk score.
Part of the Gryd Lock project. Interface defined, live oracle not yet wired.
| Error | Code | Meaning | Typical Cause |
|---|---|---|---|
| OracleUnavailableError | ORACLE_UNAVAILABLE | The oracle could not be reached. | Network outage, RPC unavailable |
| OracleTimeoutError | ORACLE_TIMEOUT | The oracle request timed out. | Slow network or unresponsive RPC |
| InvalidDestinationError | INVALID_DESTINATION | The supplied Stellar destination is invalid. | Malformed address or asset identifier |
| UnrecognizedDestinationError | UNRECOGNIZED_DESTINATION | The destination is valid but not recognized. | Destination not present in oracle data |
| ContractIncompatibilityError | CONTRACT_INCOMPATIBILITY | The adapter is incompatible with the oracle contract. | ABI/version mismatch |