Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 7x 8x 8x 8x 8x 8x | /**
* Hub A — the originating scheme's switch.
*
* The interesting behaviour is what it holds that the corridor does not carry.
* It reserves local liquidity the moment it submits, and that reservation is
* retained locally and released only if the settlement fails — the same
* withhold-then-escalate shape as the receiving hub, seen from the side that
* takes the risk first (PRD R10).
*
* It prices the corridor and nothing else. The originating scheme keeps no FX
* provider of its own — one provider per corridor, asked by the receiving
* scheme's hub, which is the shape the reference flow has — so the cross-scheme
* quote is the only price it records, and a disputed rate traces back to the
* quote that produced it.
*
* The status is set on the reply explicitly; a returned `{status, body}` object
* would be answered with HTTP 200 by fastify, so a cross-border failure would
* never reach the payer and R15's origin would be lost (see `payer.ts` for the
* full note).
*/
import {bindLeg} from '../src/context.ts';
import type {Logger} from '../src/logger.ts';
import {hop, type Participant} from './participant.ts';
export interface HubAOptions {
proxyUrl: string;
}
export function installHubA(participant: Participant, options: HubAOptions): void {
const {app} = participant;
/**
* A **request-scoped** logger for one protocol step, for the same reason as in
* `hub.ts`: the withheld bag lives on the logger, so a shared one would let one
* execution's liquidity reservation ride another execution's failure.
*/
const stepLogger = (): Logger => participant.logger.child({});
app.post('/parties', async (request, reply) => {
const traceId = participant.traceFrom(request);
const flowId = participant.flowFrom(request);
const leg = participant.legFrom(request);
const logger = stepLogger();
const result = await participant.run(traceId, flowId, leg, () =>
participant.phase('discovery', async () => {
// The receipt carries the caller's leg: a call is an edge only when
// both ends of it are observed, and this is the far end of the
// payer's `/parties` call (PRD R22).
logger.info('party lookup received', {
req: {operation: 'POST', target: '/parties'},
});
const forwarded = await bindLeg(
{id: 'discovery.proxy', from: 'hubA', to: 'proxy'},
async () => {
// No party directory of its own for the far scheme: everything
// crosses. It is the difference from the single-scheme hub that
// makes this a separate participant rather than a URL change.
logger.info('party lookup routed internationally', {
req: {operation: 'POST', target: '/parties'},
});
return hop(participant, options.proxyUrl, '/parties', request.body);
},
);
return {status: forwarded.status, body: forwarded.body};
}),
);
reply.status(result.status);
return result.body;
});
app.post('/quotes', async (request, reply) => {
const traceId = participant.traceFrom(request);
const flowId = participant.flowFrom(request);
const leg = participant.legFrom(request);
const logger = stepLogger();
const result = await participant.run(traceId, flowId, leg, () =>
participant.phase('quote', async () => {
const body = (request.body ?? {}) as {amount?: number; from?: string; to?: string};
logger.info('cross-scheme quote requested', {
amount: body.amount,
from: body.from,
to: body.to,
});
// One call, because there is one quote: the corridor's. The originating
// scheme keeps no provider of its own, so there is no local indication to
// record beside it — the price the payer settles on is this one.
const crossed = await bindLeg(
{id: 'quote.proxy', from: 'hubA', to: 'proxy'},
async () => {
logger.info('corridor quote requested', {amount: body.amount});
return hop(participant, options.proxyUrl, '/quotes', body);
},
);
logger.info('cross-scheme quote assembled', {
res: {status: crossed.status},
rate: (crossed.body as {rate?: number} | undefined)?.rate,
});
return {status: crossed.status, body: crossed.body};
}),
);
reply.status(result.status);
return result.body;
});
app.post('/transfers', async (request, reply) => {
const traceId = participant.traceFrom(request);
const flowId = participant.flowFrom(request);
const leg = participant.legFrom(request);
const logger = stepLogger();
const result = await participant.run(traceId, flowId, leg, () =>
participant.phase('transfer', async () => {
const body = (request.body ?? {}) as {amount?: number; currency?: string};
logger.info('local funds blocked', {amount: body.amount, currency: body.currency});
// Local risk taken before the corridor is asked to settle. Retained
// here and released only when something fails: on the happy path the
// reservation never leaves this participant.
logger.withhold({
liquidity: {reserved: body.amount, currency: body.currency, scheme: 'A'},
});
const started = Date.now();
const settled = await bindLeg(
{id: 'transfer.proxy', from: 'hubA', to: 'proxy'},
async () => {
logger.info('corridor settlement requested', {
amount: body.amount,
currency: body.currency,
});
return hop(participant, options.proxyUrl, '/transfers', body);
},
);
if (settled.status >= 400) {
logger.error('inter-scheme settlement failed', {
err: {message: `proxy returned ${settled.status}`},
res: {status: settled.status, elapsedMs: Date.now() - started},
});
return {status: 502, body: settled.body};
}
logger.info('inter-scheme settlement committed', {
res: {status: settled.status, elapsedMs: Date.now() - started},
});
return {status: 200, body: {status: 'settled'}};
}),
);
reply.status(result.status);
return result.body;
});
}
|