Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/runtime/http/express-router-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ import { extractClientIdentifier } from './client-identifier.ts';

const SEP10_AUTH_OPERATION_SUFFIX = ' auth';

/**
* Result of looking up an account's configured signers via an injectable
* SEP-10 account lookup. Distinguishes not-found from upstream failures.
*/
export type Sep10AccountLookupResult =
| {
status: 'found';
account: {
signers: Array<{ key: string; weight: number }>;
thresholds: { low: number; medium: number; high: number };
};
}
| { status: 'not_found' }
| { status: 'unavailable'; reason?: string };

export interface Sep10AccountLookup {
lookup(accountId: string): Promise<Sep10AccountLookupResult>;
}

/**
* Stable issuer for bearer access tokens minted by the anchor API.
* Tokens issued by a different service must not be accepted here, even when
Expand Down
8 changes: 7 additions & 1 deletion src/runtime/http/express-router.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { AnchorConfig } from '@/core/config.ts';
import { errorHandler } from '../../utils/error-handler.ts';
import { InMemoryRateLimiter, type RateLimitRule } from './rate-limiter.ts';
import type { DatabaseAdapter, WebhookProcessor } from '@/runtime/interfaces.ts';
import type {
AccountLookup,
DatabaseAdapter,
WebhookProcessor,
} from '@/runtime/interfaces.ts';
import type { RouteDefinition } from '@/types/foundation.ts';
import type { AnchorPluginHooks } from '@/types/plugin.ts';
import { Keypair } from '@stellar/stellar-sdk';
Expand All @@ -25,6 +29,7 @@ interface RouterDependencies {
depositRequestHooks?: NonNullable<AnchorPluginHooks['onDepositRequest']>[];
sep10ChallengeHooks?: NonNullable<AnchorPluginHooks['onSep10Challenge']>[];
pluginRoutes?: Array<{ pluginId: string; route: RouteDefinition }>;
accountLookup?: AccountLookup;
}

export class AnchorExpressRouter {
Expand Down Expand Up @@ -65,6 +70,7 @@ export class AnchorExpressRouter {
rateLimiter: new InMemoryRateLimiter(),
rateRules,
pluginRoutes,
accountLookup: dependencies.accountLookup,
};
}

Expand Down
24 changes: 24 additions & 0 deletions src/runtime/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,30 @@ export interface AccountSigner {
weight: number;
}

/** Signers and thresholds for a Stellar account, as reported by an account lookup source. */
export interface AccountSignerConfiguration {
account: string;
signers: AccountSigner[];
thresholds: {
low: number;
medium: number;
high: number;
};
}

export type AccountLookupResult =
| { outcome: 'found'; account: AccountSignerConfiguration }
| { outcome: 'not_found'; account: string };

/**
* Injectable boundary for loading account signers and thresholds.
* Implementations must throw on upstream failure (network, provider outage);
* an unknown account must be reported as `not_found`, never as an exception.
*/
export interface AccountLookup {
lookupAccount(account: string): Promise<AccountLookupResult>;
}

export interface AuthChallengeRecord {
id: string;
account: string;
Expand Down
26 changes: 26 additions & 0 deletions src/services/stellar-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ export interface StellarTransactionSigner {
signTransaction(transactionXdr: string, networkPassphrase: string): Promise<string>;
}

export interface StellarAccountSigner {
key: string;
weight: number;
}

export interface StellarAccountThresholds {
low: number;
medium: number;
high: number;
}

export interface StellarAccountRecord {
accountId: string;
signers: StellarAccountSigner[];
thresholds: StellarAccountThresholds;
}

export type StellarAccountLookupResult =
| { status: 'found'; account: StellarAccountRecord }
| { status: 'not_found' }
| { status: 'upstream_error'; message: string };

export interface StellarAccountLookup {
lookupAccount(accountId: string): Promise<StellarAccountLookupResult>;
}

export class LocalKeypairSigner implements StellarTransactionSigner {
readonly #keypair: Keypair;

Expand Down