Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sac-resolver

Resolve a Stellar Asset Contract (SAC) address to the asset it wraps. Cloudflare Worker + D1.

Public instance: https://sac-resolver.lightsail.network/{mainnet|testnet}/{contract_id}

GET https://sac-resolver.lightsail.network/mainnet/CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75
→ 200 {"type":"credit_alphanum4","code":"USDC","issuer":"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"}

GET https://sac-resolver.lightsail.network/mainnet/CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA
→ 200 {"type":"native"}
Status Meaning Notes
200 Resolved native / credit_alphanum4 / credit_alphanum12
404 not_a_stellar_asset_contract Contract exists but is not a SAC (wasm contract) Permanent
404 not_found The SAC has never been deployed on that network Re-checked after 15 minutes
400 Malformed path or invalid StrKey Never hits RPC
503 All RPC endpoints failed Not cached, Retry-After: 10

Debug headers: x-sac-cache: hit|miss (edge cache), x-sac-source: db|rpc.

How it resolves

  1. Fetch the contract instance entry (LedgerKeyContractInstance) with rpc.Server.getLedgerEntries.
  2. Require the executable to be StellarAsset, then read the asset from the AssetInfo instance-storage entry. If that is missing, fall back to METADATA.name (CODE:ISSUER or native).
  3. Re-derive the SAC address for that network from the parsed asset and compare it with the requested address. A mismatch is treated as not a SAC, so a contract cannot lie about which asset it represents.

Caching

Every layer exists to avoid asking RPC twice for the same address. A SAC address is sha256(network, asset), so the asset behind an address can never change. Therefore:

Result D1 Edge cache (Cache API)
ok / not_sac Permanent, RPC is never queried again s-maxage=86400 (CACHE_TTL_OK), browsers 1h
not_found checked_at is stored; served as 404 without RPC for 15 minutes (NOT_FOUND_RECHECK_S) s-maxage=900, browsers 60s
All RPCs down If a stale not_found row exists it is returned; otherwise 503 and nothing is written Not cached

Request flow: edge cache → D1 → RPC. RPC endpoints are tried in order; the next one is only used on transport/RPC errors, never on "not found".

Deploy

npm install
npx wrangler d1 create sac-resolver        # put the printed database_id into the DB binding in wrangler.jsonc
                                           # (wrangler appends a second binding; delete it and keep "DB")
npm run db:migrate:remote
npm run deploy

Local development:

npm run db:migrate:local
npm run dev
curl localhost:8787/mainnet/CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA

Verify the parsing logic directly against RPC, without the Worker:

npm run probe -- mainnet CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75
npm run probe -- testnet          # defaults to the testnet native XLM contract

Configuration

Everything lives in vars in wrangler.jsonc: RPC_URLS_MAINNET / RPC_URLS_TESTNET (comma separated, tried in order), RPC_TIMEOUT_MS, CACHE_TTL_OK. The not_found re-check interval is the constant NOT_FOUND_RECHECK_S in src/db.ts.

wrangler.jsonc contains no secrets: the D1 database_id is only usable together with an authenticated Cloudflare API token, and the RPC URLs are public. It is safe to commit. If you fork this, replace database_id with your own.

About

Resolve Stellar Asset Contract (SAC) addresses to their underlying assets. Cloudflare Workers + D1.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages