@obbyx/cli 0.1.1 → 0.1.3
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/dist/commands/balance.d.ts.map +1 -1
- package/dist/commands/balance.js +7 -5
- package/dist/commands/balance.js.map +1 -1
- package/dist/commands/pay.d.ts.map +1 -1
- package/dist/commands/pay.js +42 -29
- package/dist/commands/pay.js.map +1 -1
- package/dist/lib/channels.d.ts +6 -3
- package/dist/lib/channels.d.ts.map +1 -1
- package/dist/lib/channels.js +90 -83
- package/dist/lib/channels.js.map +1 -1
- package/dist/lib/doctor.d.ts.map +1 -1
- package/dist/lib/doctor.js +58 -49
- package/dist/lib/doctor.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/balance.ts +6 -8
- package/src/commands/pay.ts +61 -28
- package/src/lib/channels.ts +138 -114
- package/src/lib/doctor.ts +81 -68
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"balance.d.ts","sourceRoot":"","sources":["../../src/commands/balance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"balance.d.ts","sourceRoot":"","sources":["../../src/commands/balance.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtD,wBAAgB,cAAc,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,cAAc,GAAG,GAAG,CA0BjE"}
|
package/dist/commands/balance.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { toDisplay } from '@obbyx/obyte';
|
|
1
|
+
import { ObbyxError, toDisplay } from '@obbyx/obyte';
|
|
2
|
+
import { parseBalances } from '@obbyx/x402-obyte';
|
|
2
3
|
import { loadWallet } from '@obbyx/obyte/wallet';
|
|
3
4
|
import { connect, resolveNetwork } from '../runtime.js';
|
|
4
5
|
export function balanceCommand({ out, done }) {
|
|
@@ -10,11 +11,12 @@ export function balanceCommand({ out, done }) {
|
|
|
10
11
|
const wallet = loadWallet(network);
|
|
11
12
|
const client = connect(network, a.hub);
|
|
12
13
|
try {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const mine = parseBalances(await client.api.getBalances([wallet.address]), wallet.address);
|
|
15
|
+
if (mine === undefined)
|
|
16
|
+
throw new ObbyxError('ledger_unavailable', 'Malformed balances');
|
|
17
|
+
const bytes = mine['base'] ?? { stable: 0n, pending: 0n };
|
|
16
18
|
out(`${wallet.address} on ${network.caip2}`);
|
|
17
|
-
out(` GBYTE: ${toDisplay(
|
|
19
|
+
out(` GBYTE: ${toDisplay(bytes.stable, 9)} stable, ${toDisplay(bytes.pending, 9)} pending`);
|
|
18
20
|
for (const [asset, b] of Object.entries(mine)) {
|
|
19
21
|
if (asset !== 'base')
|
|
20
22
|
out(` ${asset}: ${b.stable} stable, ${b.pending} pending (atomic units)`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"balance.js","sourceRoot":"","sources":["../../src/commands/balance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"balance.js","sourceRoot":"","sources":["../../src/commands/balance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGxD,MAAM,UAAU,cAAc,CAAC,EAAE,GAAG,EAAE,IAAI,EAAkB;IAC1D,OAAO;QACL,OAAO,EAAE,SAAS;QAClB,QAAQ,EAAE,mBAAmB;QAC7B,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YACnB,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;YACnC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YACvC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;gBAC3F,IAAI,IAAI,KAAK,SAAS;oBAAE,MAAM,IAAI,UAAU,CAAC,oBAAoB,EAAE,oBAAoB,CAAC,CAAC;gBACzF,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;gBAC1D,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,OAAO,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC7C,GAAG,CACD,YAAY,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,YAAY,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,UAAU,CACxF,CAAC;gBACF,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC9C,IAAI,KAAK,KAAK,MAAM;wBAClB,GAAG,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,MAAM,YAAY,CAAC,CAAC,OAAO,yBAAyB,CAAC,CAAC;gBAC/E,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,CAAC;YACD,IAAI,CAAC,CAAC,CAAC,CAAC;QACV,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pay.d.ts","sourceRoot":"","sources":["../../src/commands/pay.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pay.d.ts","sourceRoot":"","sources":["../../src/commands/pay.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEtD,UAAU,OAAO;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,wBAAgB,UAAU,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CAAC,CAmF3E"}
|
package/dist/commands/pay.js
CHANGED
|
@@ -49,34 +49,7 @@ export function payCommand({ out, err, done }) {
|
|
|
49
49
|
const logger = cliLogger();
|
|
50
50
|
const client = connect(network, a.hub);
|
|
51
51
|
try {
|
|
52
|
-
const
|
|
53
|
-
const mechanism = new ExactObyteClient({
|
|
54
|
-
client,
|
|
55
|
-
network: network.caip2,
|
|
56
|
-
privateKey: wallet.privateKey,
|
|
57
|
-
logger,
|
|
58
|
-
composer,
|
|
59
|
-
maxFee,
|
|
60
|
-
});
|
|
61
|
-
// Use a payment channel to this seller when one is open (instant, no fee per call).
|
|
62
|
-
const batch = new BatchObyteClient({
|
|
63
|
-
client,
|
|
64
|
-
network,
|
|
65
|
-
privateKey: wallet.privateKey,
|
|
66
|
-
store: new ClientChannelStore(channelStorePath(network)),
|
|
67
|
-
logger,
|
|
68
|
-
maxAmount: new Map([['base', max]]),
|
|
69
|
-
composer,
|
|
70
|
-
});
|
|
71
|
-
const headers = {};
|
|
72
|
-
for (const h of a.header ?? []) {
|
|
73
|
-
const i = h.indexOf(':');
|
|
74
|
-
if (i <= 0)
|
|
75
|
-
throw new ObbyxError('usage', `Bad --header "${h}" (expected "Name: value")`);
|
|
76
|
-
headers[h.slice(0, i).trim()] = h.slice(i + 1).trim();
|
|
77
|
-
}
|
|
78
|
-
if (a.data !== undefined)
|
|
79
|
-
headers['content-type'] ??= 'application/json';
|
|
52
|
+
const headers = parseHeaders(a.header ?? [], a.data !== undefined);
|
|
80
53
|
const method = a.method ?? (a.data === undefined ? 'GET' : 'POST');
|
|
81
54
|
try {
|
|
82
55
|
const result = await payingFetch(a.url, {
|
|
@@ -85,7 +58,7 @@ export function payCommand({ out, err, done }) {
|
|
|
85
58
|
...(a.data === undefined ? {} : { body: a.data }),
|
|
86
59
|
...(a.payment === undefined ? {} : { paymentHeader: a.payment }),
|
|
87
60
|
}, {
|
|
88
|
-
mechanisms: {
|
|
61
|
+
mechanisms: mechanisms({ client, network, wallet, max, maxFee, logger }),
|
|
89
62
|
network: network.caip2,
|
|
90
63
|
maxAmount: new Map([['base', max]]),
|
|
91
64
|
logger,
|
|
@@ -112,4 +85,44 @@ export function payCommand({ out, err, done }) {
|
|
|
112
85
|
},
|
|
113
86
|
};
|
|
114
87
|
}
|
|
88
|
+
/** `--header 'Name: value'` options; a body defaults to JSON. */
|
|
89
|
+
function parseHeaders(list, hasBody) {
|
|
90
|
+
const headers = {};
|
|
91
|
+
for (const h of list) {
|
|
92
|
+
const i = h.indexOf(':');
|
|
93
|
+
if (i <= 0)
|
|
94
|
+
throw new ObbyxError('usage', `Bad --header "${h}" (expected "Name: value")`);
|
|
95
|
+
headers[h.slice(0, i).trim()] = h.slice(i + 1).trim();
|
|
96
|
+
}
|
|
97
|
+
if (hasBody)
|
|
98
|
+
headers['content-type'] ??= 'application/json';
|
|
99
|
+
return headers;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The wallet's payment mechanisms: an open channel to the seller first
|
|
103
|
+
* (instant, no fee per call), otherwise an on-chain unit. One coin composer
|
|
104
|
+
* for both, so concurrent payments never pick the same coins.
|
|
105
|
+
*/
|
|
106
|
+
function mechanisms({ client, network, wallet, max, maxFee, logger, }) {
|
|
107
|
+
const composer = reservingComposer(client, network, wallet.address, logger);
|
|
108
|
+
return {
|
|
109
|
+
exact: new ExactObyteClient({
|
|
110
|
+
client,
|
|
111
|
+
network: network.caip2,
|
|
112
|
+
privateKey: wallet.privateKey,
|
|
113
|
+
logger,
|
|
114
|
+
composer,
|
|
115
|
+
maxFee,
|
|
116
|
+
}),
|
|
117
|
+
batch: new BatchObyteClient({
|
|
118
|
+
client,
|
|
119
|
+
network,
|
|
120
|
+
privateKey: wallet.privateKey,
|
|
121
|
+
store: new ClientChannelStore(channelStorePath(network)),
|
|
122
|
+
logger,
|
|
123
|
+
maxAmount: new Map([['base', max]]),
|
|
124
|
+
composer,
|
|
125
|
+
}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
115
128
|
//# sourceMappingURL=pay.js.map
|
package/dist/commands/pay.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pay.js","sourceRoot":"","sources":["../../src/commands/pay.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pay.js","sourceRoot":"","sources":["../../src/commands/pay.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/channels.d.ts
CHANGED
|
@@ -7,18 +7,21 @@ import { type ReservingComposer } from '@obbyx/x402-obyte';
|
|
|
7
7
|
* State lives in `$OBBYX_HOME/channels-<network>.db`.
|
|
8
8
|
*/
|
|
9
9
|
export declare function channelStorePath(network: ObyteNetwork, env?: NodeJS.ProcessEnv): string;
|
|
10
|
-
|
|
10
|
+
interface ChannelArgs {
|
|
11
11
|
sub: string | undefined;
|
|
12
12
|
target: string | undefined;
|
|
13
13
|
deposit?: string;
|
|
14
14
|
amount?: string;
|
|
15
15
|
timeout?: string;
|
|
16
|
-
}
|
|
16
|
+
}
|
|
17
|
+
interface ChannelContext {
|
|
17
18
|
network: ObyteNetwork;
|
|
18
19
|
client: obyte.Client;
|
|
19
20
|
privateKey: Uint8Array;
|
|
20
21
|
logger: Logger;
|
|
21
22
|
out: (s?: string) => void;
|
|
22
23
|
composer?: ReservingComposer;
|
|
23
|
-
}
|
|
24
|
+
}
|
|
25
|
+
export declare function channelCommand(args: ChannelArgs, ctx: ChannelContext): Promise<number>;
|
|
26
|
+
export {};
|
|
24
27
|
//# sourceMappingURL=channels.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../../src/lib/channels.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAEL,KAAK,YAAY,EAIlB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAQL,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAG3B;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,YAAY,EACrB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAER;AAED,
|
|
1
|
+
{"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../../src/lib/channels.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAEL,KAAK,YAAY,EAIlB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAQL,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAG3B;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,YAAY,EACrB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAER;AAED,UAAU,WAAW;IACnB,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,cAAc;IACtB,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAYD,wBAAsB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAwB5F"}
|
package/dist/lib/channels.js
CHANGED
|
@@ -9,97 +9,104 @@ import { obbyxHome } from '@obbyx/obyte/wallet';
|
|
|
9
9
|
export function channelStorePath(network, env = process.env) {
|
|
10
10
|
return join(obbyxHome(env), `channels-${network.name}.db`);
|
|
11
11
|
}
|
|
12
|
+
const gbyte = (v) => `${toDisplay(v, 9)} GBYTE`;
|
|
12
13
|
export async function channelCommand(args, ctx) {
|
|
14
|
+
const run = SUBCOMMANDS[args.sub ?? ''];
|
|
15
|
+
if (run === undefined) {
|
|
16
|
+
throw new ObbyxError('usage', `channel ${Object.keys(SUBCOMMANDS).join('|')}`);
|
|
17
|
+
}
|
|
13
18
|
const store = new ClientChannelStore(channelStorePath(ctx.network));
|
|
14
|
-
const opts = {
|
|
15
|
-
client: ctx.client,
|
|
16
|
-
network: ctx.network,
|
|
17
|
-
privateKey: ctx.privateKey,
|
|
18
|
-
store,
|
|
19
|
-
logger: ctx.logger,
|
|
20
|
-
...(ctx.composer ? { composer: ctx.composer } : {}),
|
|
21
|
-
};
|
|
22
|
-
const { out } = ctx;
|
|
23
|
-
const gbyte = (v) => `${toDisplay(v, 9)} GBYTE`;
|
|
24
19
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
out(`Channel ${aa} opened to ${args.target}`);
|
|
40
|
-
out(` deposit: ${args.deposit} GBYTE, dispute window: ${timeout} s`);
|
|
41
|
-
out(` unit: ${unit}\n ${explorerUnitUrl(ctx.network, unit)}`);
|
|
42
|
-
out('Usable once the deposit is stable (minutes under load, up to ~2 h when the network is quiet): obbyx channel status ' +
|
|
43
|
-
aa);
|
|
44
|
-
return 0;
|
|
45
|
-
}
|
|
46
|
-
case 'deposit': {
|
|
47
|
-
const ch = need(store, args.target);
|
|
48
|
-
if (args.amount === undefined)
|
|
49
|
-
throw new ObbyxError('usage', 'channel deposit <aa> --amount <GBYTE>');
|
|
50
|
-
const unit = await deposit(opts, ch.aa, fromDisplay(args.amount, 9));
|
|
51
|
-
out(`Deposited ${args.amount} GBYTE to ${ch.aa}: ${unit}`);
|
|
52
|
-
return 0;
|
|
53
|
-
}
|
|
54
|
-
case 'list': {
|
|
55
|
-
const all = store.list().filter((c) => c.network === ctx.network.caip2);
|
|
56
|
-
if (all.length === 0)
|
|
57
|
-
out('No channels. Open one with: obbyx channel open <seller> --deposit <GBYTE>');
|
|
58
|
-
for (const c of all) {
|
|
59
|
-
out(`${c.aa} seller ${c.payTo} period ${c.period} spent ${gbyte(c.committed)}${c.pending ? ' (voucher pending)' : ''}`);
|
|
60
|
-
}
|
|
61
|
-
return 0;
|
|
62
|
-
}
|
|
63
|
-
case 'status': {
|
|
64
|
-
const ch = need(store, args.target);
|
|
65
|
-
const vars = await hubCall(() => ctx.client.api.getAaStateVars({ address: ch.aa }), 15_000);
|
|
66
|
-
if (vars.kind !== 'ok')
|
|
67
|
-
throw new ObbyxError('ledger_unavailable', vars.error);
|
|
68
|
-
const v = vars.value;
|
|
69
|
-
out(`Channel ${ch.aa} → seller ${ch.payTo}`);
|
|
70
|
-
out(` on-chain: period ${v['period'] ?? '-'}, deposit ${gbyte(BigInt(Number(v['balanceB'] ?? 0)))}` +
|
|
71
|
-
(v['close_initiated_by']
|
|
72
|
-
? `, CLOSING (initiated by ${v['close_initiated_by'] === 'A' ? 'seller' : 'you'})`
|
|
73
|
-
: ''));
|
|
74
|
-
out(` local: period ${ch.period}, spent ${gbyte(ch.committed)}${ch.pending ? `, pending voucher ${ch.pending.amountSpent}` : ''}`);
|
|
75
|
-
if (v['period'] === undefined)
|
|
76
|
-
out(' The deposit is not credited yet (waiting for stability).');
|
|
77
|
-
return 0;
|
|
78
|
-
}
|
|
79
|
-
case 'close': {
|
|
80
|
-
const ch = need(store, args.target);
|
|
81
|
-
if (ch.pending) {
|
|
82
|
-
throw new ObbyxError('payment_pending', 'A voucher has an unknown outcome; retry that request before closing');
|
|
83
|
-
}
|
|
84
|
-
const unit = await closeChannel(opts, ch.aa, ch.period, ch.committed);
|
|
85
|
-
out(`Close requested for period ${ch.period}, declaring ${gbyte(ch.committed)} spent: ${unit}`);
|
|
86
|
-
out(`The seller confirms at once if this is right; otherwise run \`obbyx channel confirm ${ch.aa}\` after ${ch.timeout} s.`);
|
|
87
|
-
return 0;
|
|
88
|
-
}
|
|
89
|
-
case 'confirm': {
|
|
90
|
-
const ch = need(store, args.target);
|
|
91
|
-
const unit = await confirmClose(opts, ch.aa, ch.period);
|
|
92
|
-
out(`Close confirmed for period ${ch.period}: ${unit}`);
|
|
93
|
-
return 0;
|
|
94
|
-
}
|
|
95
|
-
default:
|
|
96
|
-
throw new ObbyxError('usage', 'channel open|deposit|list|status|close|confirm');
|
|
97
|
-
}
|
|
20
|
+
await run({
|
|
21
|
+
args,
|
|
22
|
+
ctx,
|
|
23
|
+
store,
|
|
24
|
+
opts: {
|
|
25
|
+
client: ctx.client,
|
|
26
|
+
network: ctx.network,
|
|
27
|
+
privateKey: ctx.privateKey,
|
|
28
|
+
store,
|
|
29
|
+
logger: ctx.logger,
|
|
30
|
+
...(ctx.composer ? { composer: ctx.composer } : {}),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
return 0;
|
|
98
34
|
}
|
|
99
35
|
finally {
|
|
100
36
|
store.close();
|
|
101
37
|
}
|
|
102
38
|
}
|
|
39
|
+
const SUBCOMMANDS = {
|
|
40
|
+
open: openCommand,
|
|
41
|
+
deposit: depositCommand,
|
|
42
|
+
list: listCommand,
|
|
43
|
+
status: statusCommand,
|
|
44
|
+
close: closeCommand,
|
|
45
|
+
confirm: confirmCommand,
|
|
46
|
+
};
|
|
47
|
+
async function openCommand({ args, ctx, opts }) {
|
|
48
|
+
if (args.target === undefined)
|
|
49
|
+
throw new ObbyxError('usage', 'channel open <seller address> --deposit <GBYTE>');
|
|
50
|
+
if (args.deposit === undefined)
|
|
51
|
+
throw new ObbyxError('usage', 'channel open needs --deposit <GBYTE>');
|
|
52
|
+
const timeout = Number(args.timeout ?? 86_400);
|
|
53
|
+
if (!Number.isInteger(timeout) || timeout < 60)
|
|
54
|
+
throw new ObbyxError('usage', '--window must be ≥ 60 seconds');
|
|
55
|
+
const { aa, unit } = await openChannel(opts, {
|
|
56
|
+
payTo: args.target,
|
|
57
|
+
deposit: fromDisplay(args.deposit, 9),
|
|
58
|
+
timeout,
|
|
59
|
+
});
|
|
60
|
+
ctx.out(`Channel ${aa} opened to ${args.target}`);
|
|
61
|
+
ctx.out(` deposit: ${args.deposit} GBYTE, dispute window: ${timeout} s`);
|
|
62
|
+
ctx.out(` unit: ${unit}\n ${explorerUnitUrl(ctx.network, unit)}`);
|
|
63
|
+
ctx.out('Usable once the deposit is stable (minutes under load, up to ~2 h when the network is quiet): obbyx channel status ' +
|
|
64
|
+
aa);
|
|
65
|
+
}
|
|
66
|
+
async function depositCommand({ args, ctx, store, opts }) {
|
|
67
|
+
const ch = need(store, args.target);
|
|
68
|
+
if (args.amount === undefined)
|
|
69
|
+
throw new ObbyxError('usage', 'channel deposit <aa> --amount <GBYTE>');
|
|
70
|
+
const unit = await deposit(opts, ch.aa, fromDisplay(args.amount, 9));
|
|
71
|
+
ctx.out(`Deposited ${args.amount} GBYTE to ${ch.aa}: ${unit}`);
|
|
72
|
+
}
|
|
73
|
+
async function listCommand({ ctx, store }) {
|
|
74
|
+
const all = store.list().filter((c) => c.network === ctx.network.caip2);
|
|
75
|
+
if (all.length === 0)
|
|
76
|
+
ctx.out('No channels. Open one with: obbyx channel open <seller> --deposit <GBYTE>');
|
|
77
|
+
for (const c of all) {
|
|
78
|
+
ctx.out(`${c.aa} seller ${c.payTo} period ${c.period} spent ${gbyte(c.committed)}${c.pending ? ' (voucher pending)' : ''}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function statusCommand({ args, ctx, store }) {
|
|
82
|
+
const ch = need(store, args.target);
|
|
83
|
+
const vars = await hubCall(() => ctx.client.api.getAaStateVars({ address: ch.aa }), 15_000);
|
|
84
|
+
if (vars.kind !== 'ok')
|
|
85
|
+
throw new ObbyxError('ledger_unavailable', vars.error);
|
|
86
|
+
const v = vars.value;
|
|
87
|
+
ctx.out(`Channel ${ch.aa} → seller ${ch.payTo}`);
|
|
88
|
+
ctx.out(` on-chain: period ${v['period'] ?? '-'}, deposit ${gbyte(BigInt(Number(v['balanceB'] ?? 0)))}` +
|
|
89
|
+
(v['close_initiated_by']
|
|
90
|
+
? `, CLOSING (initiated by ${v['close_initiated_by'] === 'A' ? 'seller' : 'you'})`
|
|
91
|
+
: ''));
|
|
92
|
+
ctx.out(` local: period ${ch.period}, spent ${gbyte(ch.committed)}${ch.pending ? `, pending voucher ${ch.pending.amountSpent}` : ''}`);
|
|
93
|
+
if (v['period'] === undefined)
|
|
94
|
+
ctx.out(' The deposit is not credited yet (waiting for stability).');
|
|
95
|
+
}
|
|
96
|
+
async function closeCommand({ args, ctx, store, opts }) {
|
|
97
|
+
const ch = need(store, args.target);
|
|
98
|
+
if (ch.pending) {
|
|
99
|
+
throw new ObbyxError('payment_pending', 'A voucher has an unknown outcome; retry that request before closing');
|
|
100
|
+
}
|
|
101
|
+
const unit = await closeChannel(opts, ch.aa, ch.period, ch.committed);
|
|
102
|
+
ctx.out(`Close requested for period ${ch.period}, declaring ${gbyte(ch.committed)} spent: ${unit}`);
|
|
103
|
+
ctx.out(`The seller confirms at once if this is right; otherwise run \`obbyx channel confirm ${ch.aa}\` after ${ch.timeout} s.`);
|
|
104
|
+
}
|
|
105
|
+
async function confirmCommand({ args, ctx, store, opts }) {
|
|
106
|
+
const ch = need(store, args.target);
|
|
107
|
+
const unit = await confirmClose(opts, ch.aa, ch.period);
|
|
108
|
+
ctx.out(`Close confirmed for period ${ch.period}: ${unit}`);
|
|
109
|
+
}
|
|
103
110
|
function need(store, aa) {
|
|
104
111
|
if (aa === undefined)
|
|
105
112
|
throw new ObbyxError('usage', 'channel address required');
|
package/dist/lib/channels.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/lib/channels.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EACL,UAAU,EAEV,eAAe,EACf,WAAW,EACX,SAAS,GACV,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,OAAO,EACP,WAAW,GAEZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhD;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAqB,EACrB,MAAyB,OAAO,CAAC,GAAG;IAEpC,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,YAAY,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC;AAC7D,CAAC;
|
|
1
|
+
{"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/lib/channels.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/lib/doctor.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAa,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/lib/doctor.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAa,MAAM,cAAc,CAAC;AAe5D,wBAAsB,MAAM,CAC1B,OAAO,EAAE,YAAY,EACrB,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACrE,OAAO,CAAC,MAAM,CAAC,CAWjB"}
|
package/dist/lib/doctor.js
CHANGED
|
@@ -1,59 +1,71 @@
|
|
|
1
|
-
import { existsSync, statSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import obyte from 'obyte';
|
|
4
4
|
import { toDisplay } from '@obbyx/obyte';
|
|
5
|
-
import { ClientChannelStore, CoinReservations, hubCall } from '@obbyx/x402-obyte';
|
|
5
|
+
import { ClientChannelStore, CoinReservations, hubCall, parseBalances } from '@obbyx/x402-obyte';
|
|
6
6
|
import { channelStorePath } from './channels.js';
|
|
7
7
|
import { obbyxHome, walletPath } from '@obbyx/obyte/wallet';
|
|
8
|
+
const MARK = { ok: '✓', warn: '!', fail: '✗' };
|
|
9
|
+
const HUB_TIMEOUT_MS = 10_000;
|
|
8
10
|
export async function doctor(network, opts) {
|
|
9
11
|
const results = [];
|
|
10
12
|
const report = (status, what, detail = '') => {
|
|
11
13
|
results.push(status);
|
|
12
|
-
opts.out(`${status
|
|
14
|
+
opts.out(`${MARK[status]} ${what}${detail ? ` — ${detail}` : ''}`);
|
|
13
15
|
};
|
|
14
|
-
|
|
16
|
+
const address = checkWallet(network, report);
|
|
17
|
+
await checkHub(network, opts.hub ?? network.defaultHubUrl, address, report);
|
|
18
|
+
checkLocalState(network, report);
|
|
19
|
+
if (opts.facilitator !== undefined)
|
|
20
|
+
await checkFacilitator(network, opts.facilitator, report);
|
|
21
|
+
return results.includes('fail') ? 1 : 0;
|
|
22
|
+
}
|
|
23
|
+
/** The wallet file exists and only its owner can read it; returns its address. */
|
|
24
|
+
function checkWallet(network, report) {
|
|
15
25
|
const wallet = walletPath(network);
|
|
16
|
-
let address;
|
|
17
26
|
if (!existsSync(wallet)) {
|
|
18
27
|
report('fail', 'wallet', `missing; run: obbyx init --network ${network.name}`);
|
|
28
|
+
return undefined;
|
|
19
29
|
}
|
|
20
|
-
|
|
30
|
+
if ((statSync(wallet).mode & 0o077) !== 0) {
|
|
21
31
|
report('fail', 'wallet', `${wallet} is readable by others; run: chmod 600 ${wallet}`);
|
|
32
|
+
return undefined;
|
|
22
33
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const client = new obyte.Client(
|
|
34
|
+
const address = JSON.parse(readFileSync(wallet, 'utf8')).address;
|
|
35
|
+
report('ok', 'wallet', `${address} (${wallet})`);
|
|
36
|
+
return address;
|
|
37
|
+
}
|
|
38
|
+
/** The hub answers; then the wallet's bytes balance (empty on testnet is only a warning). */
|
|
39
|
+
async function checkHub(network, hubUrl, address, report) {
|
|
40
|
+
const client = new obyte.Client(hubUrl, { testnet: network.testnet });
|
|
30
41
|
try {
|
|
31
42
|
const started = Date.now();
|
|
32
|
-
const mci = await hubCall(() => client.api.getLastMci(),
|
|
43
|
+
const mci = await hubCall(() => client.api.getLastMci(), HUB_TIMEOUT_MS);
|
|
33
44
|
if (mci.kind !== 'ok') {
|
|
34
|
-
report('fail', 'hub', `${
|
|
45
|
+
report('fail', 'hub', `${hubUrl}: ${mci.error}`);
|
|
46
|
+
return;
|
|
35
47
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const pending = BigInt(base?.pending ?? 0);
|
|
44
|
-
report(stable === 0n ? (network.testnet ? 'warn' : 'fail') : 'ok', 'balance', `${toDisplay(stable, 9)} GBYTE stable, ${toDisplay(pending, 9)} pending` +
|
|
45
|
-
(stable === 0n && network.testnet ? '; run: obbyx faucet' : ''));
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
report('warn', 'balance', b.error);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
48
|
+
report('ok', 'hub', `${hubUrl}, last MCI ${mci.value}, ${Date.now() - started} ms`);
|
|
49
|
+
if (address === undefined)
|
|
50
|
+
return;
|
|
51
|
+
const b = await hubCall(() => client.api.getBalances([address]), HUB_TIMEOUT_MS);
|
|
52
|
+
if (b.kind !== 'ok') {
|
|
53
|
+
report('warn', 'balance', b.error);
|
|
54
|
+
return;
|
|
51
55
|
}
|
|
56
|
+
const base = parseBalances(b.value, address)?.['base'];
|
|
57
|
+
const stable = base?.stable ?? 0n;
|
|
58
|
+
const pending = base?.pending ?? 0n;
|
|
59
|
+
const empty = stable === 0n;
|
|
60
|
+
report(empty ? (network.testnet ? 'warn' : 'fail') : 'ok', 'balance', `${toDisplay(stable, 9)} GBYTE stable, ${toDisplay(pending, 9)} pending` +
|
|
61
|
+
(empty && network.testnet ? '; run: obbyx faucet' : ''));
|
|
52
62
|
}
|
|
53
63
|
finally {
|
|
54
64
|
client.close();
|
|
55
65
|
}
|
|
56
|
-
|
|
66
|
+
}
|
|
67
|
+
/** Coin reservations and channels with an unknown outcome (warnings, never failures). */
|
|
68
|
+
function checkLocalState(network, report) {
|
|
57
69
|
const coinsPath = join(obbyxHome(), `coins-${network.name}.db`);
|
|
58
70
|
if (existsSync(coinsPath)) {
|
|
59
71
|
const reservations = new CoinReservations(coinsPath);
|
|
@@ -74,24 +86,21 @@ export async function doctor(network, opts) {
|
|
|
74
86
|
? `, ${pending.length} with a voucher of unknown outcome (retry that request with --payment)`
|
|
75
87
|
: ''));
|
|
76
88
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
report('fail', 'facilitator', `${opts.facilitator}: ${err instanceof Error ? err.message : String(err)}`);
|
|
93
|
-
}
|
|
89
|
+
}
|
|
90
|
+
/** The facilitator answers /supported and serves this network. */
|
|
91
|
+
async function checkFacilitator(network, facilitator, report) {
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch(new URL('/supported', facilitator), {
|
|
94
|
+
signal: AbortSignal.timeout(HUB_TIMEOUT_MS),
|
|
95
|
+
});
|
|
96
|
+
const supported = (await res.json());
|
|
97
|
+
const kinds = (supported.kinds ?? [])
|
|
98
|
+
.filter((k) => k.network === network.caip2)
|
|
99
|
+
.map((k) => k.scheme);
|
|
100
|
+
report(kinds.length > 0 ? 'ok' : 'fail', 'facilitator', kinds.length > 0 ? `${facilitator}: ${kinds.join(', ')}` : `does not serve ${network.caip2}`);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
report('fail', 'facilitator', `${facilitator}: ${err instanceof Error ? err.message : String(err)}`);
|
|
94
104
|
}
|
|
95
|
-
return results.includes('fail') ? 1 : 0;
|
|
96
105
|
}
|
|
97
106
|
//# sourceMappingURL=doctor.js.map
|
package/dist/lib/doctor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/lib/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/lib/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAqB,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAS5D,MAAM,IAAI,GAA2B,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvE,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,OAAqB,EACrB,IAAsE;IAEtE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAW,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,EAAE,EAAE,EAAE;QACnD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5E,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;QAAE,MAAM,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC9F,OAAO,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,kFAAkF;AAClF,SAAS,WAAW,CAAC,OAAqB,EAAE,MAAc;IACxD,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,sCAAsC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/E,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,0CAA0C,MAAM,EAAE,CAAC,CAAC;QACtF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAyB,CAAC,OAAO,CAAC;IAC1F,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC;IACjD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,6FAA6F;AAC7F,KAAK,UAAU,QAAQ,CACrB,OAAqB,EACrB,MAAc,EACd,OAA2B,EAC3B,MAAc;IAEd,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,cAAc,CAAC,CAAC;QACzE,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACtB,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YACjD,OAAO;QACT,CAAC;QACD,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,cAAc,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,KAAK,CAAC,CAAC;QACpF,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO;QAClC,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;QACjF,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,MAAM,KAAK,EAAE,CAAC;QAC5B,MAAM,CACJ,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAClD,SAAS,EACT,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,kBAAkB,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,UAAU;YACtE,CAAC,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAAC,CAC1D,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED,yFAAyF;AACzF,SAAS,eAAe,CAAC,OAAqB,EAAE,MAAc;IAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC;IAChE,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,MAAM,YAAY,GAAG,IAAI,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACrD,YAAY,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,CACJ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAClC,mBAAmB,EACnB,KAAK,CAAC,MAAM,KAAK,CAAC;YAChB,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,qHAAqH,CACzI,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,kBAAkB,CAAC,YAAY,CAAC,CAAC;QACtD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;QACxE,QAAQ,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC;QACvD,MAAM,CACJ,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EACpC,UAAU,EACV,GAAG,IAAI,CAAC,MAAM,aAAa;YACzB,CAAC,OAAO,CAAC,MAAM;gBACb,CAAC,CAAC,KAAK,OAAO,CAAC,MAAM,wEAAwE;gBAC7F,CAAC,CAAC,EAAE,CAAC,CACV,CAAC;IACJ,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,KAAK,UAAU,gBAAgB,CAC7B,OAAqB,EACrB,WAAmB,EACnB,MAAc;IAEd,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE;YAC1D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC;SAC5C,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAsD,CAAC;QAC1F,MAAM,KAAK,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC;aAClC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,KAAK,CAAC;aAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACxB,MAAM,CACJ,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAChC,aAAa,EACb,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,OAAO,CAAC,KAAK,EAAE,CAC7F,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CACJ,MAAM,EACN,aAAa,EACb,GAAG,WAAW,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACtE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obbyx/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "obbyx command line: Obyte wallet, testnet faucet, paying x402 APIs, payment channels, diagnostics",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"x402",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@obbyx/obyte": "^0.1.
|
|
41
|
-
"@obbyx/sdk": "^0.
|
|
42
|
-
"@obbyx/x402-obyte": "^0.2.
|
|
40
|
+
"@obbyx/obyte": "^0.1.1",
|
|
41
|
+
"@obbyx/sdk": "^0.2.1",
|
|
42
|
+
"@obbyx/x402-obyte": "^0.2.2",
|
|
43
43
|
"@x402/core": "^2.29.0",
|
|
44
44
|
"obyte": "0.2.1",
|
|
45
45
|
"yargs": "^18.2.0"
|
package/src/commands/balance.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { toDisplay } from '@obbyx/obyte';
|
|
1
|
+
import { ObbyxError, toDisplay } from '@obbyx/obyte';
|
|
2
|
+
import { parseBalances } from '@obbyx/x402-obyte';
|
|
2
3
|
import { loadWallet } from '@obbyx/obyte/wallet';
|
|
3
4
|
import { connect, resolveNetwork } from '../runtime.js';
|
|
4
5
|
import type { CommandContext, Cmd } from './types.js';
|
|
@@ -12,15 +13,12 @@ export function balanceCommand({ out, done }: CommandContext): Cmd {
|
|
|
12
13
|
const wallet = loadWallet(network);
|
|
13
14
|
const client = connect(network, a.hub);
|
|
14
15
|
try {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
>;
|
|
19
|
-
const mine = balances[wallet.address] ?? {};
|
|
20
|
-
const bytes = mine['base'] ?? { stable: 0, pending: 0 };
|
|
16
|
+
const mine = parseBalances(await client.api.getBalances([wallet.address]), wallet.address);
|
|
17
|
+
if (mine === undefined) throw new ObbyxError('ledger_unavailable', 'Malformed balances');
|
|
18
|
+
const bytes = mine['base'] ?? { stable: 0n, pending: 0n };
|
|
21
19
|
out(`${wallet.address} on ${network.caip2}`);
|
|
22
20
|
out(
|
|
23
|
-
` GBYTE: ${toDisplay(
|
|
21
|
+
` GBYTE: ${toDisplay(bytes.stable, 9)} stable, ${toDisplay(bytes.pending, 9)} pending`,
|
|
24
22
|
);
|
|
25
23
|
for (const [asset, b] of Object.entries(mine)) {
|
|
26
24
|
if (asset !== 'base')
|
package/src/commands/pay.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type obyte from 'obyte';
|
|
2
|
+
import { ObbyxError, type ObyteNetwork, fromDisplay } from '@obbyx/obyte';
|
|
3
|
+
import type { Logger } from '@obbyx/obyte/logger';
|
|
2
4
|
import { loadWallet } from '@obbyx/obyte/wallet';
|
|
3
5
|
import { BatchObyteClient, ClientChannelStore, ExactObyteClient } from '@obbyx/x402-obyte';
|
|
4
6
|
import { PaymentPendingError, payingFetch } from '@obbyx/sdk';
|
|
@@ -71,32 +73,7 @@ export function payCommand({ out, err, done }: CommandContext): Cmd<PayArgs> {
|
|
|
71
73
|
const logger = cliLogger();
|
|
72
74
|
const client = connect(network, a.hub);
|
|
73
75
|
try {
|
|
74
|
-
const
|
|
75
|
-
const mechanism = new ExactObyteClient({
|
|
76
|
-
client,
|
|
77
|
-
network: network.caip2,
|
|
78
|
-
privateKey: wallet.privateKey,
|
|
79
|
-
logger,
|
|
80
|
-
composer,
|
|
81
|
-
maxFee,
|
|
82
|
-
});
|
|
83
|
-
// Use a payment channel to this seller when one is open (instant, no fee per call).
|
|
84
|
-
const batch = new BatchObyteClient({
|
|
85
|
-
client,
|
|
86
|
-
network,
|
|
87
|
-
privateKey: wallet.privateKey,
|
|
88
|
-
store: new ClientChannelStore(channelStorePath(network)),
|
|
89
|
-
logger,
|
|
90
|
-
maxAmount: new Map([['base', max]]),
|
|
91
|
-
composer,
|
|
92
|
-
});
|
|
93
|
-
const headers: Record<string, string> = {};
|
|
94
|
-
for (const h of a.header ?? []) {
|
|
95
|
-
const i = h.indexOf(':');
|
|
96
|
-
if (i <= 0) throw new ObbyxError('usage', `Bad --header "${h}" (expected "Name: value")`);
|
|
97
|
-
headers[h.slice(0, i).trim()] = h.slice(i + 1).trim();
|
|
98
|
-
}
|
|
99
|
-
if (a.data !== undefined) headers['content-type'] ??= 'application/json';
|
|
76
|
+
const headers = parseHeaders(a.header ?? [], a.data !== undefined);
|
|
100
77
|
const method = a.method ?? (a.data === undefined ? 'GET' : 'POST');
|
|
101
78
|
try {
|
|
102
79
|
const result = await payingFetch(
|
|
@@ -108,7 +85,7 @@ export function payCommand({ out, err, done }: CommandContext): Cmd<PayArgs> {
|
|
|
108
85
|
...(a.payment === undefined ? {} : { paymentHeader: a.payment }),
|
|
109
86
|
},
|
|
110
87
|
{
|
|
111
|
-
mechanisms: {
|
|
88
|
+
mechanisms: mechanisms({ client, network, wallet, max, maxFee, logger }),
|
|
112
89
|
network: network.caip2,
|
|
113
90
|
maxAmount: new Map([['base', max]]),
|
|
114
91
|
logger,
|
|
@@ -133,3 +110,59 @@ export function payCommand({ out, err, done }: CommandContext): Cmd<PayArgs> {
|
|
|
133
110
|
},
|
|
134
111
|
};
|
|
135
112
|
}
|
|
113
|
+
|
|
114
|
+
/** `--header 'Name: value'` options; a body defaults to JSON. */
|
|
115
|
+
function parseHeaders(list: readonly string[], hasBody: boolean): Record<string, string> {
|
|
116
|
+
const headers: Record<string, string> = {};
|
|
117
|
+
for (const h of list) {
|
|
118
|
+
const i = h.indexOf(':');
|
|
119
|
+
if (i <= 0) throw new ObbyxError('usage', `Bad --header "${h}" (expected "Name: value")`);
|
|
120
|
+
headers[h.slice(0, i).trim()] = h.slice(i + 1).trim();
|
|
121
|
+
}
|
|
122
|
+
if (hasBody) headers['content-type'] ??= 'application/json';
|
|
123
|
+
return headers;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The wallet's payment mechanisms: an open channel to the seller first
|
|
128
|
+
* (instant, no fee per call), otherwise an on-chain unit. One coin composer
|
|
129
|
+
* for both, so concurrent payments never pick the same coins.
|
|
130
|
+
*/
|
|
131
|
+
function mechanisms({
|
|
132
|
+
client,
|
|
133
|
+
network,
|
|
134
|
+
wallet,
|
|
135
|
+
max,
|
|
136
|
+
maxFee,
|
|
137
|
+
logger,
|
|
138
|
+
}: {
|
|
139
|
+
client: obyte.Client;
|
|
140
|
+
network: ObyteNetwork;
|
|
141
|
+
wallet: { address: string; privateKey: Uint8Array };
|
|
142
|
+
/** Most this call may cost (atomic units of bytes). */
|
|
143
|
+
max: bigint;
|
|
144
|
+
/** Most network fee an on-chain payment may cost (bytes). */
|
|
145
|
+
maxFee: bigint;
|
|
146
|
+
logger: Logger;
|
|
147
|
+
}): { exact: ExactObyteClient; batch: BatchObyteClient } {
|
|
148
|
+
const composer = reservingComposer(client, network, wallet.address, logger);
|
|
149
|
+
return {
|
|
150
|
+
exact: new ExactObyteClient({
|
|
151
|
+
client,
|
|
152
|
+
network: network.caip2,
|
|
153
|
+
privateKey: wallet.privateKey,
|
|
154
|
+
logger,
|
|
155
|
+
composer,
|
|
156
|
+
maxFee,
|
|
157
|
+
}),
|
|
158
|
+
batch: new BatchObyteClient({
|
|
159
|
+
client,
|
|
160
|
+
network,
|
|
161
|
+
privateKey: wallet.privateKey,
|
|
162
|
+
store: new ClientChannelStore(channelStorePath(network)),
|
|
163
|
+
logger,
|
|
164
|
+
maxAmount: new Map([['base', max]]),
|
|
165
|
+
composer,
|
|
166
|
+
}),
|
|
167
|
+
};
|
|
168
|
+
}
|
package/src/lib/channels.ts
CHANGED
|
@@ -31,127 +31,151 @@ export function channelStorePath(
|
|
|
31
31
|
return join(obbyxHome(env), `channels-${network.name}.db`);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
interface ChannelArgs {
|
|
35
|
+
sub: string | undefined;
|
|
36
|
+
target: string | undefined;
|
|
37
|
+
deposit?: string;
|
|
38
|
+
amount?: string;
|
|
39
|
+
timeout?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface ChannelContext {
|
|
43
|
+
network: ObyteNetwork;
|
|
44
|
+
client: obyte.Client;
|
|
45
|
+
privateKey: Uint8Array;
|
|
46
|
+
logger: Logger;
|
|
47
|
+
out: (s?: string) => void;
|
|
48
|
+
composer?: ReservingComposer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** What a subcommand gets: the parsed arguments, the context, the open store and client options. */
|
|
52
|
+
interface ChannelRun {
|
|
53
|
+
readonly args: ChannelArgs;
|
|
54
|
+
readonly ctx: ChannelContext;
|
|
55
|
+
readonly store: ClientChannelStore;
|
|
56
|
+
readonly opts: BatchClientOptions;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const gbyte = (v: bigint) => `${toDisplay(v, 9)} GBYTE`;
|
|
60
|
+
|
|
61
|
+
export async function channelCommand(args: ChannelArgs, ctx: ChannelContext): Promise<number> {
|
|
62
|
+
const run = SUBCOMMANDS[args.sub ?? ''];
|
|
63
|
+
if (run === undefined) {
|
|
64
|
+
throw new ObbyxError('usage', `channel ${Object.keys(SUBCOMMANDS).join('|')}`);
|
|
65
|
+
}
|
|
51
66
|
const store = new ClientChannelStore(channelStorePath(ctx.network));
|
|
52
|
-
const opts: BatchClientOptions = {
|
|
53
|
-
client: ctx.client,
|
|
54
|
-
network: ctx.network,
|
|
55
|
-
privateKey: ctx.privateKey,
|
|
56
|
-
store,
|
|
57
|
-
logger: ctx.logger,
|
|
58
|
-
...(ctx.composer ? { composer: ctx.composer } : {}),
|
|
59
|
-
};
|
|
60
|
-
const { out } = ctx;
|
|
61
|
-
const gbyte = (v: bigint) => `${toDisplay(v, 9)} GBYTE`;
|
|
62
67
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
out(`Channel ${aa} opened to ${args.target}`);
|
|
78
|
-
out(` deposit: ${args.deposit} GBYTE, dispute window: ${timeout} s`);
|
|
79
|
-
out(` unit: ${unit}\n ${explorerUnitUrl(ctx.network, unit)}`);
|
|
80
|
-
out(
|
|
81
|
-
'Usable once the deposit is stable (minutes under load, up to ~2 h when the network is quiet): obbyx channel status ' +
|
|
82
|
-
aa,
|
|
83
|
-
);
|
|
84
|
-
return 0;
|
|
85
|
-
}
|
|
86
|
-
case 'deposit': {
|
|
87
|
-
const ch = need(store, args.target);
|
|
88
|
-
if (args.amount === undefined)
|
|
89
|
-
throw new ObbyxError('usage', 'channel deposit <aa> --amount <GBYTE>');
|
|
90
|
-
const unit = await deposit(opts, ch.aa, fromDisplay(args.amount, 9));
|
|
91
|
-
out(`Deposited ${args.amount} GBYTE to ${ch.aa}: ${unit}`);
|
|
92
|
-
return 0;
|
|
93
|
-
}
|
|
94
|
-
case 'list': {
|
|
95
|
-
const all = store.list().filter((c) => c.network === ctx.network.caip2);
|
|
96
|
-
if (all.length === 0)
|
|
97
|
-
out('No channels. Open one with: obbyx channel open <seller> --deposit <GBYTE>');
|
|
98
|
-
for (const c of all) {
|
|
99
|
-
out(
|
|
100
|
-
`${c.aa} seller ${c.payTo} period ${c.period} spent ${gbyte(c.committed)}${c.pending ? ' (voucher pending)' : ''}`,
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
return 0;
|
|
104
|
-
}
|
|
105
|
-
case 'status': {
|
|
106
|
-
const ch = need(store, args.target);
|
|
107
|
-
const vars = await hubCall(() => ctx.client.api.getAaStateVars({ address: ch.aa }), 15_000);
|
|
108
|
-
if (vars.kind !== 'ok') throw new ObbyxError('ledger_unavailable', vars.error);
|
|
109
|
-
const v = vars.value as Record<string, number | string | undefined>;
|
|
110
|
-
out(`Channel ${ch.aa} → seller ${ch.payTo}`);
|
|
111
|
-
out(
|
|
112
|
-
` on-chain: period ${v['period'] ?? '-'}, deposit ${gbyte(BigInt(Number(v['balanceB'] ?? 0)))}` +
|
|
113
|
-
(v['close_initiated_by']
|
|
114
|
-
? `, CLOSING (initiated by ${v['close_initiated_by'] === 'A' ? 'seller' : 'you'})`
|
|
115
|
-
: ''),
|
|
116
|
-
);
|
|
117
|
-
out(
|
|
118
|
-
` local: period ${ch.period}, spent ${gbyte(ch.committed)}${ch.pending ? `, pending voucher ${ch.pending.amountSpent}` : ''}`,
|
|
119
|
-
);
|
|
120
|
-
if (v['period'] === undefined)
|
|
121
|
-
out(' The deposit is not credited yet (waiting for stability).');
|
|
122
|
-
return 0;
|
|
123
|
-
}
|
|
124
|
-
case 'close': {
|
|
125
|
-
const ch = need(store, args.target);
|
|
126
|
-
if (ch.pending) {
|
|
127
|
-
throw new ObbyxError(
|
|
128
|
-
'payment_pending',
|
|
129
|
-
'A voucher has an unknown outcome; retry that request before closing',
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
const unit = await closeChannel(opts, ch.aa, ch.period, ch.committed);
|
|
133
|
-
out(
|
|
134
|
-
`Close requested for period ${ch.period}, declaring ${gbyte(ch.committed)} spent: ${unit}`,
|
|
135
|
-
);
|
|
136
|
-
out(
|
|
137
|
-
`The seller confirms at once if this is right; otherwise run \`obbyx channel confirm ${ch.aa}\` after ${ch.timeout} s.`,
|
|
138
|
-
);
|
|
139
|
-
return 0;
|
|
140
|
-
}
|
|
141
|
-
case 'confirm': {
|
|
142
|
-
const ch = need(store, args.target);
|
|
143
|
-
const unit = await confirmClose(opts, ch.aa, ch.period);
|
|
144
|
-
out(`Close confirmed for period ${ch.period}: ${unit}`);
|
|
145
|
-
return 0;
|
|
146
|
-
}
|
|
147
|
-
default:
|
|
148
|
-
throw new ObbyxError('usage', 'channel open|deposit|list|status|close|confirm');
|
|
149
|
-
}
|
|
68
|
+
await run({
|
|
69
|
+
args,
|
|
70
|
+
ctx,
|
|
71
|
+
store,
|
|
72
|
+
opts: {
|
|
73
|
+
client: ctx.client,
|
|
74
|
+
network: ctx.network,
|
|
75
|
+
privateKey: ctx.privateKey,
|
|
76
|
+
store,
|
|
77
|
+
logger: ctx.logger,
|
|
78
|
+
...(ctx.composer ? { composer: ctx.composer } : {}),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
return 0;
|
|
150
82
|
} finally {
|
|
151
83
|
store.close();
|
|
152
84
|
}
|
|
153
85
|
}
|
|
154
86
|
|
|
87
|
+
const SUBCOMMANDS: Record<string, (run: ChannelRun) => Promise<void>> = {
|
|
88
|
+
open: openCommand,
|
|
89
|
+
deposit: depositCommand,
|
|
90
|
+
list: listCommand,
|
|
91
|
+
status: statusCommand,
|
|
92
|
+
close: closeCommand,
|
|
93
|
+
confirm: confirmCommand,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
async function openCommand({ args, ctx, opts }: ChannelRun): Promise<void> {
|
|
97
|
+
if (args.target === undefined)
|
|
98
|
+
throw new ObbyxError('usage', 'channel open <seller address> --deposit <GBYTE>');
|
|
99
|
+
if (args.deposit === undefined)
|
|
100
|
+
throw new ObbyxError('usage', 'channel open needs --deposit <GBYTE>');
|
|
101
|
+
const timeout = Number(args.timeout ?? 86_400);
|
|
102
|
+
if (!Number.isInteger(timeout) || timeout < 60)
|
|
103
|
+
throw new ObbyxError('usage', '--window must be ≥ 60 seconds');
|
|
104
|
+
const { aa, unit } = await openChannel(opts, {
|
|
105
|
+
payTo: args.target,
|
|
106
|
+
deposit: fromDisplay(args.deposit, 9),
|
|
107
|
+
timeout,
|
|
108
|
+
});
|
|
109
|
+
ctx.out(`Channel ${aa} opened to ${args.target}`);
|
|
110
|
+
ctx.out(` deposit: ${args.deposit} GBYTE, dispute window: ${timeout} s`);
|
|
111
|
+
ctx.out(` unit: ${unit}\n ${explorerUnitUrl(ctx.network, unit)}`);
|
|
112
|
+
ctx.out(
|
|
113
|
+
'Usable once the deposit is stable (minutes under load, up to ~2 h when the network is quiet): obbyx channel status ' +
|
|
114
|
+
aa,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function depositCommand({ args, ctx, store, opts }: ChannelRun): Promise<void> {
|
|
119
|
+
const ch = need(store, args.target);
|
|
120
|
+
if (args.amount === undefined)
|
|
121
|
+
throw new ObbyxError('usage', 'channel deposit <aa> --amount <GBYTE>');
|
|
122
|
+
const unit = await deposit(opts, ch.aa, fromDisplay(args.amount, 9));
|
|
123
|
+
ctx.out(`Deposited ${args.amount} GBYTE to ${ch.aa}: ${unit}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function listCommand({ ctx, store }: ChannelRun): Promise<void> {
|
|
127
|
+
const all = store.list().filter((c) => c.network === ctx.network.caip2);
|
|
128
|
+
if (all.length === 0)
|
|
129
|
+
ctx.out('No channels. Open one with: obbyx channel open <seller> --deposit <GBYTE>');
|
|
130
|
+
for (const c of all) {
|
|
131
|
+
ctx.out(
|
|
132
|
+
`${c.aa} seller ${c.payTo} period ${c.period} spent ${gbyte(c.committed)}${c.pending ? ' (voucher pending)' : ''}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function statusCommand({ args, ctx, store }: ChannelRun): Promise<void> {
|
|
138
|
+
const ch = need(store, args.target);
|
|
139
|
+
const vars = await hubCall(() => ctx.client.api.getAaStateVars({ address: ch.aa }), 15_000);
|
|
140
|
+
if (vars.kind !== 'ok') throw new ObbyxError('ledger_unavailable', vars.error);
|
|
141
|
+
const v = vars.value as Record<string, number | string | undefined>;
|
|
142
|
+
ctx.out(`Channel ${ch.aa} → seller ${ch.payTo}`);
|
|
143
|
+
ctx.out(
|
|
144
|
+
` on-chain: period ${v['period'] ?? '-'}, deposit ${gbyte(BigInt(Number(v['balanceB'] ?? 0)))}` +
|
|
145
|
+
(v['close_initiated_by']
|
|
146
|
+
? `, CLOSING (initiated by ${v['close_initiated_by'] === 'A' ? 'seller' : 'you'})`
|
|
147
|
+
: ''),
|
|
148
|
+
);
|
|
149
|
+
ctx.out(
|
|
150
|
+
` local: period ${ch.period}, spent ${gbyte(ch.committed)}${ch.pending ? `, pending voucher ${ch.pending.amountSpent}` : ''}`,
|
|
151
|
+
);
|
|
152
|
+
if (v['period'] === undefined)
|
|
153
|
+
ctx.out(' The deposit is not credited yet (waiting for stability).');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function closeCommand({ args, ctx, store, opts }: ChannelRun): Promise<void> {
|
|
157
|
+
const ch = need(store, args.target);
|
|
158
|
+
if (ch.pending) {
|
|
159
|
+
throw new ObbyxError(
|
|
160
|
+
'payment_pending',
|
|
161
|
+
'A voucher has an unknown outcome; retry that request before closing',
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const unit = await closeChannel(opts, ch.aa, ch.period, ch.committed);
|
|
165
|
+
ctx.out(
|
|
166
|
+
`Close requested for period ${ch.period}, declaring ${gbyte(ch.committed)} spent: ${unit}`,
|
|
167
|
+
);
|
|
168
|
+
ctx.out(
|
|
169
|
+
`The seller confirms at once if this is right; otherwise run \`obbyx channel confirm ${ch.aa}\` after ${ch.timeout} s.`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function confirmCommand({ args, ctx, store, opts }: ChannelRun): Promise<void> {
|
|
174
|
+
const ch = need(store, args.target);
|
|
175
|
+
const unit = await confirmClose(opts, ch.aa, ch.period);
|
|
176
|
+
ctx.out(`Close confirmed for period ${ch.period}: ${unit}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
155
179
|
function need(store: ClientChannelStore, aa: string | undefined) {
|
|
156
180
|
if (aa === undefined) throw new ObbyxError('usage', 'channel address required');
|
|
157
181
|
const ch = store.get(aa);
|
package/src/lib/doctor.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { existsSync, statSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import obyte from 'obyte';
|
|
4
4
|
import { type ObyteNetwork, toDisplay } from '@obbyx/obyte';
|
|
5
|
-
import { ClientChannelStore, CoinReservations, hubCall } from '@obbyx/x402-obyte';
|
|
5
|
+
import { ClientChannelStore, CoinReservations, hubCall, parseBalances } from '@obbyx/x402-obyte';
|
|
6
6
|
import { channelStorePath } from './channels.js';
|
|
7
7
|
import { obbyxHome, walletPath } from '@obbyx/obyte/wallet';
|
|
8
8
|
|
|
@@ -11,69 +11,82 @@ import { obbyxHome, walletPath } from '@obbyx/obyte/wallet';
|
|
|
11
11
|
* Exit code 0 when nothing failed (warnings allowed).
|
|
12
12
|
*/
|
|
13
13
|
type Status = 'ok' | 'warn' | 'fail';
|
|
14
|
+
type Report = (status: Status, what: string, detail?: string) => void;
|
|
15
|
+
|
|
16
|
+
const MARK: Record<Status, string> = { ok: '✓', warn: '!', fail: '✗' };
|
|
17
|
+
const HUB_TIMEOUT_MS = 10_000;
|
|
14
18
|
|
|
15
19
|
export async function doctor(
|
|
16
20
|
network: ObyteNetwork,
|
|
17
21
|
opts: { hub?: string; facilitator?: string; out: (s: string) => void },
|
|
18
22
|
): Promise<number> {
|
|
19
23
|
const results: Status[] = [];
|
|
20
|
-
const report = (status
|
|
24
|
+
const report: Report = (status, what, detail = '') => {
|
|
21
25
|
results.push(status);
|
|
22
|
-
opts.out(
|
|
23
|
-
`${status === 'ok' ? '✓' : status === 'warn' ? '!' : '✗'} ${what}${detail ? ` — ${detail}` : ''}`,
|
|
24
|
-
);
|
|
26
|
+
opts.out(`${MARK[status]} ${what}${detail ? ` — ${detail}` : ''}`);
|
|
25
27
|
};
|
|
28
|
+
const address = checkWallet(network, report);
|
|
29
|
+
await checkHub(network, opts.hub ?? network.defaultHubUrl, address, report);
|
|
30
|
+
checkLocalState(network, report);
|
|
31
|
+
if (opts.facilitator !== undefined) await checkFacilitator(network, opts.facilitator, report);
|
|
32
|
+
return results.includes('fail') ? 1 : 0;
|
|
33
|
+
}
|
|
26
34
|
|
|
27
|
-
|
|
35
|
+
/** The wallet file exists and only its owner can read it; returns its address. */
|
|
36
|
+
function checkWallet(network: ObyteNetwork, report: Report): string | undefined {
|
|
28
37
|
const wallet = walletPath(network);
|
|
29
|
-
let address: string | undefined;
|
|
30
38
|
if (!existsSync(wallet)) {
|
|
31
39
|
report('fail', 'wallet', `missing; run: obbyx init --network ${network.name}`);
|
|
32
|
-
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
if ((statSync(wallet).mode & 0o077) !== 0) {
|
|
33
43
|
report('fail', 'wallet', `${wallet} is readable by others; run: chmod 600 ${wallet}`);
|
|
34
|
-
|
|
35
|
-
const { readFileSync } = await import('node:fs');
|
|
36
|
-
address = (JSON.parse(readFileSync(wallet, 'utf8')) as { address: string }).address;
|
|
37
|
-
report('ok', 'wallet', `${address} (${wallet})`);
|
|
44
|
+
return undefined;
|
|
38
45
|
}
|
|
46
|
+
const address = (JSON.parse(readFileSync(wallet, 'utf8')) as { address: string }).address;
|
|
47
|
+
report('ok', 'wallet', `${address} (${wallet})`);
|
|
48
|
+
return address;
|
|
49
|
+
}
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
/** The hub answers; then the wallet's bytes balance (empty on testnet is only a warning). */
|
|
52
|
+
async function checkHub(
|
|
53
|
+
network: ObyteNetwork,
|
|
54
|
+
hubUrl: string,
|
|
55
|
+
address: string | undefined,
|
|
56
|
+
report: Report,
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
const client = new obyte.Client(hubUrl, { testnet: network.testnet });
|
|
42
59
|
try {
|
|
43
60
|
const started = Date.now();
|
|
44
|
-
const mci = await hubCall(() => client.api.getLastMci(),
|
|
61
|
+
const mci = await hubCall(() => client.api.getLastMci(), HUB_TIMEOUT_MS);
|
|
45
62
|
if (mci.kind !== 'ok') {
|
|
46
|
-
report('fail', 'hub', `${
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
}
|
|
63
|
+
report('fail', 'hub', `${hubUrl}: ${mci.error}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
report('ok', 'hub', `${hubUrl}, last MCI ${mci.value}, ${Date.now() - started} ms`);
|
|
67
|
+
if (address === undefined) return;
|
|
68
|
+
const b = await hubCall(() => client.api.getBalances([address]), HUB_TIMEOUT_MS);
|
|
69
|
+
if (b.kind !== 'ok') {
|
|
70
|
+
report('warn', 'balance', b.error);
|
|
71
|
+
return;
|
|
71
72
|
}
|
|
73
|
+
const base = parseBalances(b.value, address)?.['base'];
|
|
74
|
+
const stable = base?.stable ?? 0n;
|
|
75
|
+
const pending = base?.pending ?? 0n;
|
|
76
|
+
const empty = stable === 0n;
|
|
77
|
+
report(
|
|
78
|
+
empty ? (network.testnet ? 'warn' : 'fail') : 'ok',
|
|
79
|
+
'balance',
|
|
80
|
+
`${toDisplay(stable, 9)} GBYTE stable, ${toDisplay(pending, 9)} pending` +
|
|
81
|
+
(empty && network.testnet ? '; run: obbyx faucet' : ''),
|
|
82
|
+
);
|
|
72
83
|
} finally {
|
|
73
84
|
client.close();
|
|
74
85
|
}
|
|
86
|
+
}
|
|
75
87
|
|
|
76
|
-
|
|
88
|
+
/** Coin reservations and channels with an unknown outcome (warnings, never failures). */
|
|
89
|
+
function checkLocalState(network: ObyteNetwork, report: Report): void {
|
|
77
90
|
const coinsPath = join(obbyxHome(), `coins-${network.name}.db`);
|
|
78
91
|
if (existsSync(coinsPath)) {
|
|
79
92
|
const reservations = new CoinReservations(coinsPath);
|
|
@@ -102,32 +115,32 @@ export async function doctor(
|
|
|
102
115
|
: ''),
|
|
103
116
|
);
|
|
104
117
|
}
|
|
118
|
+
}
|
|
105
119
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
/** The facilitator answers /supported and serves this network. */
|
|
121
|
+
async function checkFacilitator(
|
|
122
|
+
network: ObyteNetwork,
|
|
123
|
+
facilitator: string,
|
|
124
|
+
report: Report,
|
|
125
|
+
): Promise<void> {
|
|
126
|
+
try {
|
|
127
|
+
const res = await fetch(new URL('/supported', facilitator), {
|
|
128
|
+
signal: AbortSignal.timeout(HUB_TIMEOUT_MS),
|
|
129
|
+
});
|
|
130
|
+
const supported = (await res.json()) as { kinds?: { scheme: string; network: string }[] };
|
|
131
|
+
const kinds = (supported.kinds ?? [])
|
|
132
|
+
.filter((k) => k.network === network.caip2)
|
|
133
|
+
.map((k) => k.scheme);
|
|
134
|
+
report(
|
|
135
|
+
kinds.length > 0 ? 'ok' : 'fail',
|
|
136
|
+
'facilitator',
|
|
137
|
+
kinds.length > 0 ? `${facilitator}: ${kinds.join(', ')}` : `does not serve ${network.caip2}`,
|
|
138
|
+
);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
report(
|
|
141
|
+
'fail',
|
|
142
|
+
'facilitator',
|
|
143
|
+
`${facilitator}: ${err instanceof Error ? err.message : String(err)}`,
|
|
144
|
+
);
|
|
130
145
|
}
|
|
131
|
-
|
|
132
|
-
return results.includes('fail') ? 1 : 0;
|
|
133
146
|
}
|