@obbyx/cli 0.0.0-stage → 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +24 -2
- package/dist/cli.d.ts +24 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +117 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/address.d.ts +3 -0
- package/dist/commands/address.d.ts.map +1 -0
- package/dist/commands/address.js +13 -0
- package/dist/commands/address.js.map +1 -0
- package/dist/commands/balance.d.ts +3 -0
- package/dist/commands/balance.d.ts.map +1 -0
- package/dist/commands/balance.js +30 -0
- package/dist/commands/balance.js.map +1 -0
- package/dist/commands/channel-cli.d.ts +11 -0
- package/dist/commands/channel-cli.d.ts.map +1 -0
- package/dist/commands/channel-cli.js +77 -0
- package/dist/commands/channel-cli.js.map +1 -0
- package/dist/commands/channel.d.ts +11 -0
- package/dist/commands/channel.d.ts.map +1 -0
- package/dist/commands/channel.js +77 -0
- package/dist/commands/channel.js.map +1 -0
- package/dist/commands/decode.d.ts +5 -0
- package/dist/commands/decode.d.ts.map +1 -0
- package/dist/commands/decode.js +17 -0
- package/dist/commands/decode.js.map +1 -0
- package/dist/commands/doctor-command.d.ts +5 -0
- package/dist/commands/doctor-command.d.ts.map +1 -0
- package/dist/commands/doctor-command.js +17 -0
- package/dist/commands/doctor-command.js.map +1 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +17 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/faucet.d.ts +5 -0
- package/dist/commands/faucet.d.ts.map +1 -0
- package/dist/commands/faucet.js +42 -0
- package/dist/commands/faucet.js.map +1 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +26 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/logs-command.d.ts +12 -0
- package/dist/commands/logs-command.d.ts.map +1 -0
- package/dist/commands/logs-command.js +32 -0
- package/dist/commands/logs-command.js.map +1 -0
- package/dist/commands/logs.d.ts +12 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +32 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/pay.d.ts +14 -0
- package/dist/commands/pay.d.ts.map +1 -0
- package/dist/commands/pay.js +115 -0
- package/dist/commands/pay.js.map +1 -0
- package/dist/commands/types.d.ts +27 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/faucet/device.d.ts +74 -0
- package/dist/faucet/device.d.ts.map +1 -0
- package/dist/faucet/device.js +222 -0
- package/dist/faucet/device.js.map +1 -0
- package/dist/faucet/faucet.d.ts +40 -0
- package/dist/faucet/faucet.d.ts.map +1 -0
- package/dist/faucet/faucet.js +111 -0
- package/dist/faucet/faucet.js.map +1 -0
- package/dist/lib/channels.d.ts +24 -0
- package/dist/lib/channels.d.ts.map +1 -0
- package/dist/lib/channels.js +111 -0
- package/dist/lib/channels.js.map +1 -0
- package/dist/lib/doctor.d.ts +7 -0
- package/dist/lib/doctor.d.ts.map +1 -0
- package/dist/lib/doctor.js +97 -0
- package/dist/lib/doctor.js.map +1 -0
- package/dist/lib/logs.d.ts +16 -0
- package/dist/lib/logs.d.ts.map +1 -0
- package/dist/lib/logs.js +64 -0
- package/dist/lib/logs.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +11 -0
- package/dist/main.js.map +1 -0
- package/dist/runtime.d.ts +21 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +86 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +52 -3
- package/src/cli.ts +139 -0
- package/src/commands/address.ts +14 -0
- package/src/commands/balance.ts +35 -0
- package/src/commands/channel.ts +96 -0
- package/src/commands/decode.ts +19 -0
- package/src/commands/doctor.ts +21 -0
- package/src/commands/faucet.ts +48 -0
- package/src/commands/init.ts +28 -0
- package/src/commands/logs.ts +48 -0
- package/src/commands/pay.ts +135 -0
- package/src/commands/types.ts +31 -0
- package/src/faucet/device.ts +330 -0
- package/src/faucet/faucet.ts +141 -0
- package/src/lib/channels.ts +161 -0
- package/src/lib/doctor.ts +133 -0
- package/src/lib/logs.ts +79 -0
- package/src/main.ts +14 -0
- package/src/runtime.ts +123 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import obyte from 'obyte';
|
|
4
|
+
import { type ObyteNetwork, toDisplay } from '@obbyx/obyte';
|
|
5
|
+
import { ClientChannelStore, CoinReservations, hubCall } from '@obbyx/x402-obyte';
|
|
6
|
+
import { channelStorePath } from './channels.js';
|
|
7
|
+
import { obbyxHome, walletPath } from '@obbyx/obyte/wallet';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `obbyx doctor` — checks everything a payment depends on and says what to fix.
|
|
11
|
+
* Exit code 0 when nothing failed (warnings allowed).
|
|
12
|
+
*/
|
|
13
|
+
type Status = 'ok' | 'warn' | 'fail';
|
|
14
|
+
|
|
15
|
+
export async function doctor(
|
|
16
|
+
network: ObyteNetwork,
|
|
17
|
+
opts: { hub?: string; facilitator?: string; out: (s: string) => void },
|
|
18
|
+
): Promise<number> {
|
|
19
|
+
const results: Status[] = [];
|
|
20
|
+
const report = (status: Status, what: string, detail = '') => {
|
|
21
|
+
results.push(status);
|
|
22
|
+
opts.out(
|
|
23
|
+
`${status === 'ok' ? '✓' : status === 'warn' ? '!' : '✗'} ${what}${detail ? ` — ${detail}` : ''}`,
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Wallet
|
|
28
|
+
const wallet = walletPath(network);
|
|
29
|
+
let address: string | undefined;
|
|
30
|
+
if (!existsSync(wallet)) {
|
|
31
|
+
report('fail', 'wallet', `missing; run: obbyx init --network ${network.name}`);
|
|
32
|
+
} else if ((statSync(wallet).mode & 0o077) !== 0) {
|
|
33
|
+
report('fail', 'wallet', `${wallet} is readable by others; run: chmod 600 ${wallet}`);
|
|
34
|
+
} else {
|
|
35
|
+
const { readFileSync } = await import('node:fs');
|
|
36
|
+
address = (JSON.parse(readFileSync(wallet, 'utf8')) as { address: string }).address;
|
|
37
|
+
report('ok', 'wallet', `${address} (${wallet})`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Hub
|
|
41
|
+
const client = new obyte.Client(opts.hub ?? network.defaultHubUrl, { testnet: network.testnet });
|
|
42
|
+
try {
|
|
43
|
+
const started = Date.now();
|
|
44
|
+
const mci = await hubCall(() => client.api.getLastMci(), 10_000);
|
|
45
|
+
if (mci.kind !== 'ok') {
|
|
46
|
+
report('fail', 'hub', `${opts.hub ?? network.defaultHubUrl}: ${mci.error}`);
|
|
47
|
+
} else {
|
|
48
|
+
report(
|
|
49
|
+
'ok',
|
|
50
|
+
'hub',
|
|
51
|
+
`${opts.hub ?? network.defaultHubUrl}, last MCI ${mci.value}, ${Date.now() - started} ms`,
|
|
52
|
+
);
|
|
53
|
+
if (address !== undefined) {
|
|
54
|
+
const b = await hubCall(() => client.api.getBalances([address]), 10_000);
|
|
55
|
+
if (b.kind === 'ok') {
|
|
56
|
+
const base = (
|
|
57
|
+
b.value as Record<string, Record<string, { stable: number; pending: number }>>
|
|
58
|
+
)[address]?.['base'];
|
|
59
|
+
const stable = BigInt(base?.stable ?? 0);
|
|
60
|
+
const pending = BigInt(base?.pending ?? 0);
|
|
61
|
+
report(
|
|
62
|
+
stable === 0n ? (network.testnet ? 'warn' : 'fail') : 'ok',
|
|
63
|
+
'balance',
|
|
64
|
+
`${toDisplay(stable, 9)} GBYTE stable, ${toDisplay(pending, 9)} pending` +
|
|
65
|
+
(stable === 0n && network.testnet ? '; run: obbyx faucet' : ''),
|
|
66
|
+
);
|
|
67
|
+
} else {
|
|
68
|
+
report('warn', 'balance', b.error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} finally {
|
|
73
|
+
client.close();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Local payment state
|
|
77
|
+
const coinsPath = join(obbyxHome(), `coins-${network.name}.db`);
|
|
78
|
+
if (existsSync(coinsPath)) {
|
|
79
|
+
const reservations = new CoinReservations(coinsPath);
|
|
80
|
+
const units = reservations.boundUnits(network.caip2);
|
|
81
|
+
reservations.close();
|
|
82
|
+
report(
|
|
83
|
+
units.length === 0 ? 'ok' : 'warn',
|
|
84
|
+
'coin reservations',
|
|
85
|
+
units.length === 0
|
|
86
|
+
? 'none'
|
|
87
|
+
: `${units.length} unpublished payment unit(s) hold inputs; they are released once seen on the ledger or after the publication window`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const channelsPath = channelStorePath(network);
|
|
91
|
+
if (existsSync(channelsPath)) {
|
|
92
|
+
const channels = new ClientChannelStore(channelsPath);
|
|
93
|
+
const list = channels.list().filter((c) => c.network === network.caip2);
|
|
94
|
+
channels.close();
|
|
95
|
+
const pending = list.filter((c) => c.pending !== null);
|
|
96
|
+
report(
|
|
97
|
+
pending.length === 0 ? 'ok' : 'warn',
|
|
98
|
+
'channels',
|
|
99
|
+
`${list.length} channel(s)` +
|
|
100
|
+
(pending.length
|
|
101
|
+
? `, ${pending.length} with a voucher of unknown outcome (retry that request with --payment)`
|
|
102
|
+
: ''),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Facilitator (optional)
|
|
107
|
+
if (opts.facilitator !== undefined) {
|
|
108
|
+
try {
|
|
109
|
+
const res = await fetch(new URL('/supported', opts.facilitator), {
|
|
110
|
+
signal: AbortSignal.timeout(10_000),
|
|
111
|
+
});
|
|
112
|
+
const supported = (await res.json()) as { kinds?: { scheme: string; network: string }[] };
|
|
113
|
+
const kinds = (supported.kinds ?? [])
|
|
114
|
+
.filter((k) => k.network === network.caip2)
|
|
115
|
+
.map((k) => k.scheme);
|
|
116
|
+
report(
|
|
117
|
+
kinds.length > 0 ? 'ok' : 'fail',
|
|
118
|
+
'facilitator',
|
|
119
|
+
kinds.length > 0
|
|
120
|
+
? `${opts.facilitator}: ${kinds.join(', ')}`
|
|
121
|
+
: `does not serve ${network.caip2}`,
|
|
122
|
+
);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
report(
|
|
125
|
+
'fail',
|
|
126
|
+
'facilitator',
|
|
127
|
+
`${opts.facilitator}: ${err instanceof Error ? err.message : String(err)}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return results.includes('fail') ? 1 : 0;
|
|
133
|
+
}
|
package/src/lib/logs.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `obbyx logs` — follow ONE payment through the JSON logs of the seller
|
|
6
|
+
* middleware, the facilitator and the watcher (they share correlation fields:
|
|
7
|
+
* requestId, binding, proofId, aa, seq). Reads files or stdin.
|
|
8
|
+
*/
|
|
9
|
+
export interface LogFilter {
|
|
10
|
+
readonly binding?: string;
|
|
11
|
+
readonly proof?: string;
|
|
12
|
+
readonly aa?: string;
|
|
13
|
+
readonly request?: string;
|
|
14
|
+
readonly level?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
|
|
18
|
+
const SHOWN = [
|
|
19
|
+
'binding',
|
|
20
|
+
'proofId',
|
|
21
|
+
'aa',
|
|
22
|
+
'seq',
|
|
23
|
+
'reason',
|
|
24
|
+
'from',
|
|
25
|
+
'to',
|
|
26
|
+
'transaction',
|
|
27
|
+
'amountSpent',
|
|
28
|
+
'status',
|
|
29
|
+
'err',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function matches(entry: Record<string, unknown>, f: LogFilter): boolean {
|
|
33
|
+
if (f.binding !== undefined && entry['binding'] !== f.binding) return false;
|
|
34
|
+
if (f.proof !== undefined && entry['proofId'] !== f.proof && entry['unit'] !== f.proof)
|
|
35
|
+
return false;
|
|
36
|
+
if (f.aa !== undefined && entry['aa'] !== f.aa) return false;
|
|
37
|
+
if (f.request !== undefined && entry['requestId'] !== f.request && entry['reqId'] !== f.request)
|
|
38
|
+
return false;
|
|
39
|
+
if (f.level !== undefined) {
|
|
40
|
+
const min = LEVELS.indexOf(f.level);
|
|
41
|
+
if (min >= 0 && LEVELS.indexOf(String(entry['level'])) < min) return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function format(entry: Record<string, unknown>): string {
|
|
47
|
+
const time = String(entry['time'] ?? '').slice(11, 23);
|
|
48
|
+
const extra = SHOWN.filter((k) => entry[k] !== undefined)
|
|
49
|
+
.map(
|
|
50
|
+
(k) => `${k}=${typeof entry[k] === 'object' ? JSON.stringify(entry[k]) : String(entry[k])}`,
|
|
51
|
+
)
|
|
52
|
+
.join(' ');
|
|
53
|
+
return `${time} ${String(entry['level'] ?? '').padEnd(5)} ${String(entry['service'] ?? '-')}/${String(entry['module'] ?? '-')}: ${String(entry['msg'] ?? '')}${extra ? ` ${extra}` : ''}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function logs(
|
|
57
|
+
files: readonly string[],
|
|
58
|
+
filter: LogFilter,
|
|
59
|
+
out: (s: string) => void,
|
|
60
|
+
): Promise<number> {
|
|
61
|
+
const sources = files.length > 0 ? files : ['-'];
|
|
62
|
+
const found: Record<string, unknown>[] = [];
|
|
63
|
+
for (const file of sources) {
|
|
64
|
+
const input = file === '-' ? process.stdin : createReadStream(file, 'utf8');
|
|
65
|
+
for await (const line of createInterface({ input, crlfDelay: Infinity })) {
|
|
66
|
+
if (!line.startsWith('{')) continue;
|
|
67
|
+
try {
|
|
68
|
+
const entry = JSON.parse(line) as Record<string, unknown>;
|
|
69
|
+
if (matches(entry, filter)) found.push(entry);
|
|
70
|
+
} catch {
|
|
71
|
+
// not a JSON log line
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Several components' logs merged into one timeline.
|
|
76
|
+
found.sort((a, b) => String(a['time'] ?? '').localeCompare(String(b['time'] ?? '')));
|
|
77
|
+
for (const entry of found) out(format(entry));
|
|
78
|
+
return found.length > 0 ? 0 : 1;
|
|
79
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { hideBin } from 'yargs/helpers';
|
|
3
|
+
import { runCli } from './cli.js';
|
|
4
|
+
|
|
5
|
+
runCli(hideBin(process.argv), {
|
|
6
|
+
out: (line = '') => process.stdout.write(`${line}\n`),
|
|
7
|
+
err: (line) => process.stderr.write(`${line}\n`),
|
|
8
|
+
}).then(
|
|
9
|
+
(code) => process.exit(code),
|
|
10
|
+
(e: unknown) => {
|
|
11
|
+
process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
},
|
|
14
|
+
);
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import obyte from 'obyte';
|
|
3
|
+
import {
|
|
4
|
+
decodePaymentRequiredHeader,
|
|
5
|
+
decodePaymentResponseHeader,
|
|
6
|
+
decodePaymentSignatureHeader,
|
|
7
|
+
} from '@x402/core/http';
|
|
8
|
+
import {
|
|
9
|
+
OBYTE_NETWORKS,
|
|
10
|
+
ObbyxError,
|
|
11
|
+
type ObyteNetwork,
|
|
12
|
+
explorerUnitUrl,
|
|
13
|
+
getNetwork,
|
|
14
|
+
toDisplay,
|
|
15
|
+
} from '@obbyx/obyte';
|
|
16
|
+
import { type Logger, createLogger } from '@obbyx/obyte/logger';
|
|
17
|
+
import { obbyxHome } from '@obbyx/obyte/wallet';
|
|
18
|
+
import { CoinReservations, ReservingComposer } from '@obbyx/x402-obyte';
|
|
19
|
+
|
|
20
|
+
/** Helpers shared by the commands: network/hub, logger, argument parsing, output. */
|
|
21
|
+
export function printPayment(
|
|
22
|
+
err: (l: string) => void,
|
|
23
|
+
network: ObyteNetwork,
|
|
24
|
+
p: {
|
|
25
|
+
success: boolean;
|
|
26
|
+
amount?: string;
|
|
27
|
+
transaction: string;
|
|
28
|
+
errorReason?: string;
|
|
29
|
+
extra?: unknown;
|
|
30
|
+
},
|
|
31
|
+
): void {
|
|
32
|
+
const channel = p.extra as
|
|
33
|
+
| {
|
|
34
|
+
chargedAmount?: string;
|
|
35
|
+
channelState?: { channelId?: string; chargedCumulativeAmount?: string };
|
|
36
|
+
}
|
|
37
|
+
| undefined;
|
|
38
|
+
if (p.success && channel?.chargedAmount !== undefined) {
|
|
39
|
+
// batch-settlement: an off-chain voucher, no unit per call.
|
|
40
|
+
err(
|
|
41
|
+
`Paid: ${toDisplay(BigInt(channel.chargedAmount), 9)} GBYTE via channel ${channel.channelState?.channelId ?? ''}` +
|
|
42
|
+
` (total in this period: ${toDisplay(BigInt(channel.channelState?.chargedCumulativeAmount ?? '0'), 9)} GBYTE)`,
|
|
43
|
+
);
|
|
44
|
+
} else if (p.success) {
|
|
45
|
+
err(
|
|
46
|
+
`Paid: ${toDisplay(BigInt(p.amount ?? '0'), 9)} GBYTE, unit ${p.transaction}\n ${explorerUnitUrl(network, p.transaction)}`,
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
err(`Payment not completed: ${p.errorReason}${p.transaction ? ` (${p.transaction})` : ''}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function cliLogger(): Logger {
|
|
54
|
+
return createLogger({
|
|
55
|
+
level: (process.env['LOG_LEVEL'] as never) ?? 'warn',
|
|
56
|
+
pretty: process.stderr.isTTY,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function bytesArg(value: string, name: string): bigint {
|
|
61
|
+
if (!/^\d+$/.test(value))
|
|
62
|
+
throw new ObbyxError('usage', `--${name} must be a whole number of bytes`);
|
|
63
|
+
return BigInt(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function seconds(value: string, name: string): number {
|
|
67
|
+
const n = Number(value);
|
|
68
|
+
if (!Number.isInteger(n) || n <= 0 || n > 86_400) {
|
|
69
|
+
throw new ObbyxError('usage', `--${name} must be a whole number of seconds (1..86400)`);
|
|
70
|
+
}
|
|
71
|
+
return n;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Reserving coin picker for this wallet (inputs of unpublished units are never reused). */
|
|
75
|
+
export function reservingComposer(
|
|
76
|
+
client: obyte.Client,
|
|
77
|
+
network: ObyteNetwork,
|
|
78
|
+
address: string,
|
|
79
|
+
logger: Logger,
|
|
80
|
+
): ReservingComposer {
|
|
81
|
+
return new ReservingComposer({
|
|
82
|
+
client,
|
|
83
|
+
network: network.caip2,
|
|
84
|
+
address,
|
|
85
|
+
reservations: new CoinReservations(join(obbyxHome(), `coins-${network.name}.db`)),
|
|
86
|
+
logger,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function resolveNetwork(name: string): ObyteNetwork {
|
|
91
|
+
if (name === 'testnet' || name === 'livenet') return OBYTE_NETWORKS[name];
|
|
92
|
+
try {
|
|
93
|
+
return getNetwork(name);
|
|
94
|
+
} catch {
|
|
95
|
+
throw new ObbyxError('usage', `Unknown --network "${name}" (use testnet or livenet)`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function connect(network: ObyteNetwork, hub: string | undefined): obyte.Client {
|
|
100
|
+
return new obyte.Client(hub ?? network.defaultHubUrl, { testnet: network.testnet });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function decodeAny(value: string): unknown {
|
|
104
|
+
for (const [kind, decode] of [
|
|
105
|
+
['PAYMENT-SIGNATURE', decodePaymentSignatureHeader],
|
|
106
|
+
['PAYMENT-REQUIRED', decodePaymentRequiredHeader],
|
|
107
|
+
['PAYMENT-RESPONSE', decodePaymentResponseHeader],
|
|
108
|
+
] as const) {
|
|
109
|
+
try {
|
|
110
|
+
const decoded = decode(value) as Record<string, unknown>;
|
|
111
|
+
const looksRight =
|
|
112
|
+
kind === 'PAYMENT-SIGNATURE'
|
|
113
|
+
? 'payload' in decoded && 'accepted' in decoded
|
|
114
|
+
: kind === 'PAYMENT-REQUIRED'
|
|
115
|
+
? 'accepts' in decoded
|
|
116
|
+
: 'success' in decoded;
|
|
117
|
+
if (looksRight) return { header: kind, value: decoded };
|
|
118
|
+
} catch {
|
|
119
|
+
// try next
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw new ObbyxError('decode_failed', 'Not a recognizable x402 header');
|
|
123
|
+
}
|