@antseed/node 0.2.118 → 0.2.120

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 (82) hide show
  1. package/dist/discovery/display-name.d.ts +8 -0
  2. package/dist/discovery/display-name.d.ts.map +1 -0
  3. package/dist/discovery/display-name.js +17 -0
  4. package/dist/discovery/display-name.js.map +1 -0
  5. package/dist/discovery/domain-verification.d.ts.map +1 -1
  6. package/dist/discovery/domain-verification.js +2 -1
  7. package/dist/discovery/domain-verification.js.map +1 -1
  8. package/dist/discovery/github-verification.d.ts.map +1 -1
  9. package/dist/discovery/github-verification.js +5 -1
  10. package/dist/discovery/github-verification.js.map +1 -1
  11. package/dist/discovery/index.d.ts +1 -0
  12. package/dist/discovery/index.d.ts.map +1 -1
  13. package/dist/discovery/index.js +1 -0
  14. package/dist/discovery/index.js.map +1 -1
  15. package/dist/discovery/service-catalog.d.ts +2 -1
  16. package/dist/discovery/service-catalog.d.ts.map +1 -1
  17. package/dist/discovery/service-catalog.js +2 -0
  18. package/dist/discovery/service-catalog.js.map +1 -1
  19. package/dist/discovery/verifier-capabilities.d.ts +9 -0
  20. package/dist/discovery/verifier-capabilities.d.ts.map +1 -0
  21. package/dist/discovery/verifier-capabilities.js +35 -0
  22. package/dist/discovery/verifier-capabilities.js.map +1 -0
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +6 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/node.d.ts +15 -3
  28. package/dist/node.d.ts.map +1 -1
  29. package/dist/node.js +114 -82
  30. package/dist/node.js.map +1 -1
  31. package/dist/p2p/identity.js +1 -1
  32. package/dist/p2p/identity.js.map +1 -1
  33. package/dist/payments/evm/multicall.d.ts +2 -1
  34. package/dist/payments/evm/multicall.d.ts.map +1 -1
  35. package/dist/payments/evm/multicall.js +1 -1
  36. package/dist/payments/evm/multicall.js.map +1 -1
  37. package/dist/payments/evm/trust-signals-client.d.ts +54 -0
  38. package/dist/payments/evm/trust-signals-client.d.ts.map +1 -0
  39. package/dist/payments/evm/trust-signals-client.js +162 -0
  40. package/dist/payments/evm/trust-signals-client.js.map +1 -0
  41. package/dist/payments/generated-contract-addresses.d.ts +14 -4
  42. package/dist/payments/generated-contract-addresses.d.ts.map +1 -1
  43. package/dist/payments/generated-contract-addresses.js +14 -4
  44. package/dist/payments/generated-contract-addresses.js.map +1 -1
  45. package/dist/reputation/identity-history.d.ts +75 -0
  46. package/dist/reputation/identity-history.d.ts.map +1 -0
  47. package/dist/reputation/identity-history.js +258 -0
  48. package/dist/reputation/identity-history.js.map +1 -0
  49. package/dist/reputation/model-reputation.d.ts +3 -2
  50. package/dist/reputation/model-reputation.d.ts.map +1 -1
  51. package/dist/reputation/model-reputation.js +2 -9
  52. package/dist/reputation/model-reputation.js.map +1 -1
  53. package/dist/reputation/public-json.d.ts +9 -0
  54. package/dist/reputation/public-json.d.ts.map +1 -0
  55. package/dist/reputation/public-json.js +98 -0
  56. package/dist/reputation/public-json.js.map +1 -0
  57. package/dist/reputation/sybil-risk.d.ts +35 -0
  58. package/dist/reputation/sybil-risk.d.ts.map +1 -0
  59. package/dist/reputation/sybil-risk.js +136 -0
  60. package/dist/reputation/sybil-risk.js.map +1 -0
  61. package/dist/reputation/trust-score.d.ts +83 -0
  62. package/dist/reputation/trust-score.d.ts.map +1 -0
  63. package/dist/reputation/trust-score.js +91 -0
  64. package/dist/reputation/trust-score.js.map +1 -0
  65. package/dist/routing/default-router.d.ts +0 -1
  66. package/dist/routing/default-router.d.ts.map +1 -1
  67. package/dist/routing/default-router.js +2 -12
  68. package/dist/routing/default-router.js.map +1 -1
  69. package/dist/routing/model-route-ranking.d.ts +0 -1
  70. package/dist/routing/model-route-ranking.d.ts.map +1 -1
  71. package/dist/routing/model-route-ranking.js.map +1 -1
  72. package/dist/types/peer.d.ts +27 -18
  73. package/dist/types/peer.d.ts.map +1 -1
  74. package/dist/verification/tee-status.d.ts +35 -0
  75. package/dist/verification/tee-status.d.ts.map +1 -0
  76. package/dist/verification/tee-status.js +21 -0
  77. package/dist/verification/tee-status.js.map +1 -0
  78. package/package.json +14 -6
  79. package/dist/reputation/on-chain-reputation.d.ts +0 -84
  80. package/dist/reputation/on-chain-reputation.d.ts.map +0 -1
  81. package/dist/reputation/on-chain-reputation.js +0 -290
  82. package/dist/reputation/on-chain-reputation.js.map +0 -1
package/dist/node.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
+ import { IDENTITY_HISTORY_TTL_MS, IdentityHistoryCollector } from './reputation/identity-history.js';
5
+ import { computeTrustScore } from './reputation/trust-score.js';
6
+ import { TrustSignalsClient } from './payments/evm/trust-signals-client.js';
4
7
  import { loadOrCreateIdentity } from "./p2p/identity.js";
5
8
  import { peerIdToAddress } from "./types/peer.js";
6
9
  import { MeteringStorage } from "./metering/storage.js";
@@ -31,6 +34,7 @@ import { signUtf8 } from "./p2p/identity.js";
31
34
  import { BalanceManager, DepositsClient, ChannelsClient, FreeUsageClient, BuyerFreeUsageManager, SellerFreeUsageManager, StakingClient, ChannelStore, CHANNEL_KIND, CHANNEL_ROLE, CHANNEL_STATUS, } from "./payments/index.js";
32
35
  import { debugLog, debugWarn } from "./utils/debug.js";
33
36
  import { parsePublicAddress } from "./discovery/public-address.js";
37
+ import { sanitizePeerDisplayName } from "./discovery/display-name.js";
34
38
  import { BuyerPaymentManager } from "./payments/buyer-payment-manager.js";
35
39
  import { BuyerPaymentNegotiator } from "./payments/buyer-payment-negotiator.js";
36
40
  import { RpcHealthMonitor } from "./payments/rpc-health.js";
@@ -40,8 +44,16 @@ import { SellerPaymentManager } from "./payments/seller-payment-manager.js";
40
44
  import { IdentityClient } from "./payments/evm/identity-client.js";
41
45
  import { SellerRequestHandler } from "./seller-request-handler.js";
42
46
  import { BuyerRequestHandler, } from "./buyer-request-handler.js";
43
- import { buildSybilContext, computeOnChainScore, computeOnChainScoreWithRisk, computeOnChainSybilRisk, computeOnChainTrust, } from "./reputation/on-chain-reputation.js";
47
+ import { buildSybilContext, computeOnChainSybilRisk, } from "./reputation/sybil-risk.js";
44
48
  import { buyerFault } from "./errors.js";
49
+ /** Store the trust score on the peer; leaves the fields untouched when the peer is unscored. */
50
+ function applyTrust(peer) {
51
+ const trust = computeTrustScore(peer);
52
+ if (!trust)
53
+ return;
54
+ peer.trust = trust;
55
+ peer.onChainReputationScore = trust.score;
56
+ }
45
57
  const EMPTY_BUYER_USAGE = {
46
58
  totalRequests: 0,
47
59
  totalInputTokens: '0',
@@ -52,7 +64,7 @@ const EMPTY_BUYER_USAGE = {
52
64
  channels: [],
53
65
  services: [],
54
66
  };
55
- const EXTERNAL_VERIFICATION_RESULT_TTL_MS = 15 * 60_000;
67
+ const EXTERNAL_VERIFICATION_RESULT_TTL_MS = IDENTITY_HISTORY_TTL_MS;
56
68
  /** How long one relayer gets exclusive first refusal on a sweep offer before
57
69
  * it passes to the next candidate. The relayer replies 'submitted' right
58
70
  * after its local checks + one Base RPC simulation (~1-2s on healthy RPCs),
@@ -88,6 +100,8 @@ export class AntseedNode extends EventEmitter {
88
100
  _buyerFreeUsageManager = null;
89
101
  _sellerFreeUsageManager = null;
90
102
  _stakingClient = null;
103
+ /** Batched reader for the on-chain inputs of the buyer trust score. */
104
+ _trustSignalsClient = null;
91
105
  _sellerAddressResolver = null;
92
106
  _identityClient = null;
93
107
  _paymentMuxes = new Map();
@@ -126,6 +140,7 @@ export class AntseedNode extends EventEmitter {
126
140
  _partialPeerEnrichmentChain = Promise.resolve();
127
141
  /** Serializes non-blocking external claim verification for discovered peers. */
128
142
  _externalVerificationChain = Promise.resolve();
143
+ _identityHistoryCollector;
129
144
  _externalVerificationCache = new Map();
130
145
  _externalVerificationInFlight = new Set();
131
146
  constructor(config) {
@@ -387,6 +402,7 @@ export class AntseedNode extends EventEmitter {
387
402
  this._buyerFreeUsageManager = null;
388
403
  this._sellerFreeUsageManager = null;
389
404
  this._stakingClient = null;
405
+ this._trustSignalsClient = null;
390
406
  this._identityClient = null;
391
407
  this._sellerAddressResolver = null;
392
408
  this._buyerPaymentManager = null;
@@ -482,12 +498,12 @@ export class AntseedNode extends EventEmitter {
482
498
  });
483
499
  }
484
500
  _queuePartialPeerEnrichment(peers) {
485
- if (peers.length === 0 || !this._channelsClient || !this._stakingClient) {
501
+ if (peers.length === 0 || !this._trustSignalsClient) {
486
502
  return;
487
503
  }
488
- const peersToEnrich = peers.map((peer) => ({ ...peer }));
504
+ const peersToEnrich = peers;
489
505
  this._partialPeerEnrichmentChain = this._partialPeerEnrichmentChain.then(async () => {
490
- if (!this._started || !this._channelsClient || !this._stakingClient) {
506
+ if (!this._started || !this._trustSignalsClient) {
491
507
  return;
492
508
  }
493
509
  await this._enrichPeersWithOnChainStats(peersToEnrich);
@@ -522,6 +538,8 @@ export class AntseedNode extends EventEmitter {
522
538
  const queued = [];
523
539
  const nowMs = Date.now();
524
540
  for (const peer of peers) {
541
+ if (this._externalVerificationInFlight.size >= 512)
542
+ break;
525
543
  const claimsKey = this._externalVerificationClaimsKey(peer);
526
544
  if (!claimsKey)
527
545
  continue;
@@ -535,7 +553,7 @@ export class AntseedNode extends EventEmitter {
535
553
  continue;
536
554
  }
537
555
  this._externalVerificationInFlight.add(cacheKey);
538
- queued.push({ ...peer });
556
+ queued.push(peer);
539
557
  }
540
558
  if (queued.length === 0)
541
559
  return;
@@ -594,17 +612,19 @@ export class AntseedNode extends EventEmitter {
594
612
  domains,
595
613
  github,
596
614
  };
615
+ this._identityHistoryCollector ??= new IdentityHistoryCollector();
616
+ results.identityHistory = await this._identityHistoryCollector.collect(results);
617
+ if (this._externalVerificationCache.size >= 512) {
618
+ this._externalVerificationCache.delete(this._externalVerificationCache.keys().next().value);
619
+ }
597
620
  this._externalVerificationCache.set(peer.peerId, {
598
621
  claimsKey,
599
622
  checkedAtMs,
600
623
  results,
601
624
  });
602
- const verifiedPeer = { ...peer, verificationResults: results };
603
- const risk = typeof verifiedPeer.onChainSybilRisk === 'number'
604
- ? verifiedPeer.onChainSybilRisk
605
- : 0;
606
- verifiedPeer.onChainReputationScore = computeOnChainScoreWithRisk(verifiedPeer, risk) ?? verifiedPeer.onChainReputationScore;
607
- return verifiedPeer;
625
+ peer.verificationResults = results;
626
+ applyTrust(peer);
627
+ return peer;
608
628
  }
609
629
  catch (err) {
610
630
  debugWarn(`[Node] External verification failed for ${peer.peerId.slice(0, 12)}...: ${err instanceof Error ? err.message : err}`);
@@ -635,7 +655,7 @@ export class AntseedNode extends EventEmitter {
635
655
  * enriches its results, so volume / last-settled / ghost counts are
636
656
  * available when chain RPC is configured.
637
657
  */
638
- async findPeer(peerId) {
658
+ async findPeer(peerId, options) {
639
659
  if (!this._peerLookup) {
640
660
  throw buyerFault("Node not started or not in buyer mode", "node-not-started");
641
661
  }
@@ -659,7 +679,12 @@ export class AntseedNode extends EventEmitter {
659
679
  const best = results.reduce((acc, r) => r.metadata.timestamp > acc.metadata.timestamp ? r : acc);
660
680
  const peer = this._lookupResultToPeerInfo(best);
661
681
  this._attachCachedExternalVerificationResults([peer]);
662
- this._queueExternalVerification([peer]);
682
+ if (options?.awaitExternalVerification && !peer.verificationResults && this._externalVerificationClaimsKey(peer)) {
683
+ await this._verifyExternalClaimsForPeer(peer);
684
+ }
685
+ else {
686
+ this._queueExternalVerification([peer]);
687
+ }
663
688
  await this._enrichPeersWithOnChainStats([peer]);
664
689
  return peer;
665
690
  }
@@ -676,81 +701,81 @@ export class AntseedNode extends EventEmitter {
676
701
  * configured — callers can safely invoke this regardless.
677
702
  */
678
703
  async _enrichPeersWithOnChainStats(peers) {
679
- if (!this._channelsClient || !this._stakingClient || peers.length === 0) {
704
+ const client = this._trustSignalsClient;
705
+ if (!client || peers.length === 0) {
680
706
  return;
681
707
  }
682
- const channelsClient = this._channelsClient;
683
- const stakingClient = this._stakingClient;
684
708
  const resolver = this._sellerAddressResolver;
685
- const DISCOVERY_RPC_CONCURRENCY = 8;
686
- // Throttle on-chain reads across rapid discovery cycles. 60s is short
687
- // enough that freshness feels real-time in the UI, and long enough that
688
- // back-to-back `discoverPeers()` calls don't hammer the RPC endpoint.
689
- const ON_CHAIN_STATS_TTL_MS = 60_000;
709
+ // Every stale peer is read in one batched pass (two Multicall3 round
710
+ // trips), so the refresh interval bounds RPC usage per discovery cycle
711
+ // instead of the peer count.
712
+ const ON_CHAIN_STATS_TTL_MS = 120_000;
690
713
  const nowMs = Date.now();
691
- const queue = peers.slice();
692
714
  const peersWithCompleteStats = new Set();
693
- const verifyOne = async (p) => {
694
- if (typeof p.onChainStatsFetchedAt === 'number'
695
- && nowMs - p.onChainStatsFetchedAt < ON_CHAIN_STATS_TTL_MS) {
715
+ const stale = [];
716
+ for (const p of peers) {
717
+ if (typeof p.onChainStatsFetchedAt === 'number' && nowMs - p.onChainStatsFetchedAt < ON_CHAIN_STATS_TTL_MS) {
696
718
  peersWithCompleteStats.add(p);
697
- return;
719
+ continue;
698
720
  }
699
721
  try {
700
- const evmAddress = resolver
722
+ const address = resolver
701
723
  ? await resolver.resolveSellerAddress(p.peerId, p.metadata)
702
724
  : peerIdToAddress(p.peerId);
703
- const [agentId, stake, stakedAt] = await Promise.all([
704
- stakingClient.getAgentId(evmAddress),
705
- stakingClient.getStake(evmAddress).catch(() => null),
706
- stakingClient.getStakedAt(evmAddress).catch(() => null),
707
- ]);
708
- const stats = await channelsClient.getAgentStats(agentId);
709
- if (stake === null || stakedAt === null) {
710
- return;
711
- }
712
- p.onChainAgentId = agentId;
713
- p.onChainStakeUsdcMicros = stake <= BigInt(Number.MAX_SAFE_INTEGER)
714
- ? Number(stake)
715
- : Number.MAX_SAFE_INTEGER;
716
- p.onChainChannelCount = stats.channelCount;
717
- p.onChainGhostCount = stats.ghostCount;
718
- // totalVolumeUsdc is base-6 USDC. Clamp to safe-int range before
719
- // narrowing to Number — ~9M USDC fits Number.MAX_SAFE_INTEGER.
720
- const volumeMicros = stats.totalVolumeUsdc;
721
- p.onChainTotalVolumeUsdcMicros = volumeMicros <= BigInt(Number.MAX_SAFE_INTEGER)
722
- ? Number(volumeMicros)
723
- : Number.MAX_SAFE_INTEGER;
724
- p.onChainLastSettledAtSec = stats.lastSettledAt;
725
- // Some migrated/facade staking accounts return zero for `stakedAt`,
726
- // and transient RPC failures used to be coerced to zero as well. A
727
- // zero read must not erase a previously verified positive timestamp.
728
- if (typeof stakedAt === 'number' && Number.isFinite(stakedAt) && stakedAt > 0) {
729
- p.onChainStakedAtSec = stakedAt;
730
- }
731
- p.onChainStatsFetchedAt = Date.now();
732
- peersWithCompleteStats.add(p);
725
+ stale.push({ peer: p, address });
733
726
  }
734
727
  catch {
735
- // Per-peer verification failure — preserve the previous complete
736
- // snapshot, or leave a newly discovered peer unenriched.
728
+ // Unresolvable seller address: leave the previous snapshot in place.
737
729
  }
738
- };
739
- const workers = [];
740
- for (let i = 0; i < Math.min(DISCOVERY_RPC_CONCURRENCY, queue.length); i++) {
741
- workers.push((async () => {
742
- for (;;) {
743
- const next = queue.shift();
744
- if (!next)
745
- return;
746
- await verifyOne(next);
747
- }
748
- })());
749
730
  }
750
- await Promise.all(workers);
751
- this._applyTrustAndSybil(peers, peersWithCompleteStats);
752
- }
753
- _applyTrustAndSybil(peers, peersToUpdate) {
731
+ if (stale.length === 0) {
732
+ this._applyTrust(peers, peersWithCompleteStats);
733
+ return;
734
+ }
735
+ let signals;
736
+ try {
737
+ signals = await client.read(stale.map((entry) => entry.address));
738
+ }
739
+ catch (err) {
740
+ debugWarn(`[Node] On-chain trust signal read failed: ${err instanceof Error ? err.message : err}`);
741
+ this._applyTrust(peers, peersWithCompleteStats);
742
+ return;
743
+ }
744
+ for (const { peer: p, address } of stale) {
745
+ const read = signals.get(address);
746
+ // A peer without an agent id (or a failed batch entry) keeps its
747
+ // previous complete snapshot, or stays unenriched when newly discovered.
748
+ if (!read)
749
+ continue;
750
+ p.onChainAgentId = read.agentId;
751
+ if (read.channelCount !== undefined)
752
+ p.onChainChannelCount = read.channelCount;
753
+ if (read.ghostCount !== undefined)
754
+ p.onChainGhostCount = read.ghostCount;
755
+ if (read.totalVolumeUsdcMicros !== undefined)
756
+ p.onChainTotalVolumeUsdcMicros = read.totalVolumeUsdcMicros;
757
+ if (read.lastSettledAtSec !== undefined)
758
+ p.onChainLastSettledAtSec = read.lastSettledAtSec;
759
+ if (read.usageEpoch !== undefined) {
760
+ p.onChainUsageEpoch = read.usageEpoch;
761
+ p.onChainUsageShareBps = read.usageShareBps;
762
+ p.onChainUsageLastEpochUsdcMicros = read.usageLastEpochUsdcMicros;
763
+ }
764
+ if (read.poolStakeAnts !== undefined)
765
+ p.onChainPoolStakeAnts = read.poolStakeAnts;
766
+ if (read.poolPowerShareBps !== undefined)
767
+ p.onChainPoolPowerShareBps = read.poolPowerShareBps;
768
+ if (read.washFlagged !== undefined)
769
+ p.onChainWashFlagged = read.washFlagged;
770
+ if (read.washShareBps !== undefined)
771
+ p.onChainWashShareBps = read.washShareBps;
772
+ p.onChainStatsFetchedAt = Date.now();
773
+ peersWithCompleteStats.add(p);
774
+ }
775
+ this._applyTrust(peers, peersWithCompleteStats);
776
+ }
777
+ /** Recompute the trust score and the display-only sybil heuristic. */
778
+ _applyTrust(peers, peersToUpdate) {
754
779
  if (peers.length === 0)
755
780
  return;
756
781
  const ctx = peers.length >= 2
@@ -759,16 +784,12 @@ export class AntseedNode extends EventEmitter {
759
784
  for (const p of peers) {
760
785
  if (peersToUpdate && !peersToUpdate.has(p))
761
786
  continue;
762
- const trust = computeOnChainTrust(p);
763
- if (trust === null)
764
- continue;
765
- p.onChainTrustScore = trust;
766
- if (ctx) {
787
+ if (ctx && typeof p.onChainChannelCount === 'number') {
767
788
  const sybil = computeOnChainSybilRisk(p, ctx);
768
789
  p.onChainSybilRisk = sybil.risk;
769
790
  p.onChainSybilFlags = sybil.flags;
770
791
  }
771
- p.onChainReputationScore = computeOnChainScore(p, ctx) ?? undefined;
792
+ applyTrust(p);
772
793
  }
773
794
  }
774
795
  /**
@@ -1620,6 +1641,16 @@ export class AntseedNode extends EventEmitter {
1620
1641
  });
1621
1642
  debugLog(`[Node] StakingClient initialized (contract=${payments.stakingAddress.slice(0, 10)}...)`);
1622
1643
  }
1644
+ if (this._channelsClient && payments.stakingAddress && payments.channelsAddress) {
1645
+ this._trustSignalsClient = new TrustSignalsClient(this._channelsClient.provider, {
1646
+ sellerRegistry: payments.stakingAddress,
1647
+ channels: payments.channelsAddress,
1648
+ ...(payments.sellerPoolsAddress ? { sellerPools: payments.sellerPoolsAddress } : {}),
1649
+ ...(payments.usageAccountingAddress ? { usageAccounting: payments.usageAccountingAddress } : {}),
1650
+ ...(payments.washTradingRegistryAddress ? { washTradingRegistry: payments.washTradingRegistryAddress } : {}),
1651
+ });
1652
+ debugLog(`[Node] TrustSignalsClient initialized (pools=${Boolean(payments.sellerPoolsAddress)} usage=${Boolean(payments.usageAccountingAddress)} wash=${Boolean(payments.washTradingRegistryAddress)})`);
1653
+ }
1623
1654
  // Initialize IdentityClient (ERC-8004 IdentityRegistry)
1624
1655
  if (payments.rpcUrl && payments.identityRegistryAddress) {
1625
1656
  this._identityClient = new IdentityClient({
@@ -1946,6 +1977,7 @@ export class AntseedNode extends EventEmitter {
1946
1977
  return `${result.host}:${result.port}`;
1947
1978
  }
1948
1979
  _lookupResultToPeerInfo(result) {
1980
+ const displayName = sanitizePeerDisplayName(result.metadata.displayName);
1949
1981
  const providers = result.metadata.providers.map((p) => p.provider);
1950
1982
  const firstProvider = result.metadata.providers[0];
1951
1983
  const providerPricingEntries = {};
@@ -2030,7 +2062,7 @@ export class AntseedNode extends EventEmitter {
2030
2062
  const hasProviderServiceCapabilities = Object.keys(providerServiceCapabilityEntries).length > 0;
2031
2063
  return {
2032
2064
  peerId: result.metadata.peerId,
2033
- displayName: result.metadata.displayName,
2065
+ displayName,
2034
2066
  // `metadata.timestamp` is signed by the seller and can reflect the
2035
2067
  // seller's wall clock, not this buyer's. Freshness validation in
2036
2068
  // PeerLookup already handles seller/buyer clock skew using the HTTP Date