@certen.io/cli 0.7.0 → 0.7.2
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/CHANGELOG.md +553 -495
- package/README.md +232 -232
- package/dist/commands/chains.js +2 -2
- package/dist/commands/pending.d.ts +19 -0
- package/dist/commands/pending.js +105 -15
- package/dist/commands/pending.js.map +1 -1
- package/dist/help-root.js +10 -10
- package/dist/index.js +36 -6
- package/dist/index.js.map +1 -1
- package/package.json +58 -58
package/README.md
CHANGED
|
@@ -1,232 +1,232 @@
|
|
|
1
|
-
# `@certen.io/cli`
|
|
2
|
-
|
|
3
|
-
The `certen` command line for the [CERTEN Gateway](https://gateway.kompendium.co/reference) —
|
|
4
|
-
proof-gated cross-chain execution on Accumulate.
|
|
5
|
-
|
|
6
|
-
**Docs: <https://docs.kompendium.co>** · **Portal: <https://gateway.kompendium.co/portal>**
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
npm install -g @certen.io/cli
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## From nothing to a proof
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
certen login # approve this machine once in the portal; the key arrives here
|
|
16
|
-
certen init # signing key, identity, funding — created and checked
|
|
17
|
-
|
|
18
|
-
certen call --identity <id> --chain base-sepolia --to 0xYourContract \
|
|
19
|
-
--fn 'confirm(bytes32)' --arg 0x… --sign-with dev --wait
|
|
20
|
-
|
|
21
|
-
certen proof get <intent-id> # the evidence, to hand to a counterparty
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**You never copy an API key.** `certen login` uses the device authorization grant: it prints a
|
|
25
|
-
short code, you approve that code in a portal session you already trust, and the CLI collects the
|
|
26
|
-
key over its own channel. The secret is never displayed here and never passes through your
|
|
27
|
-
clipboard or your shell history.
|
|
28
|
-
|
|
29
|
-
`certen init` is idempotent. It creates only what is missing, records the identity id so a later
|
|
30
|
-
run reuses it, waits until the identity can actually sign, and tells you if an abstract account
|
|
31
|
-
needs gas before it can execute anything.
|
|
32
|
-
|
|
33
|
-
Stuck at any point:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
certen doctor # names the one thing blocking you, and the command that fixes it
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### If your gateway predates device authorization
|
|
40
|
-
|
|
41
|
-
`certen login` will say so and point at the portal. Mint a key there and hand it over without
|
|
42
|
-
putting it in your shell history:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
certen auth login --api-key - # reads the key from stdin
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Your key never leaves this machine
|
|
49
|
-
|
|
50
|
-
`certen keys generate` creates an Ed25519 key, encrypts it with a passphrase (scrypt + AES-256-GCM),
|
|
51
|
-
and writes it to `~/.certen/keys/<name>.json` with `0600` permissions. The CLI sends **signatures**
|
|
52
|
-
to the gateway. It never sends a private key, and there is no code path that could.
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
certen keys generate --name dev # prompts for a passphrase (confirmed)
|
|
56
|
-
certen keys generate --name ci --no-passphrase # unencrypted; file permissions only
|
|
57
|
-
certen keys list # metadata only — never decrypts
|
|
58
|
-
certen keys show dev
|
|
59
|
-
certen keys verify dev # proves the key decrypts and signs correctly
|
|
60
|
-
certen keys sign --name dev --hash <hex> # print a signature, send nothing
|
|
61
|
-
certen keys delete dev --yes
|
|
62
|
-
certen keys path
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Set `CERTEN_KEY_PASSPHRASE` to skip the prompt in CI. When it is set and there is no TTY, the CLI
|
|
66
|
-
uses it; when neither is available it fails with an explanation rather than hanging on a prompt
|
|
67
|
-
nobody can see.
|
|
68
|
-
|
|
69
|
-
**`certen keys sign` sends nothing anywhere.** It is the air-gapped path: generate on one machine,
|
|
70
|
-
carry the hash to it, carry the signature back.
|
|
71
|
-
|
|
72
|
-
## Proof-gated contract calls
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
certen call --identity <uuid> --chain base-sepolia --to 0xTarget \
|
|
76
|
-
--fn 'confirm(bytes32)' --arg 0x… --sign-with dev --wait
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
`--fn` takes a Solidity signature and `--arg` repeats positionally. Arguments are checked against
|
|
80
|
-
the signature **before** anything is sent — on a proof-gated call, a valid proof of the *wrong*
|
|
81
|
-
call is still a valid proof, so a mis-encoded argument is worse than an error.
|
|
82
|
-
|
|
83
|
-
The ADI URL, the abstract account (`msg.sender` on chain) and the numeric chain id are all derived
|
|
84
|
-
from the identity. You do not supply them, and you should not need to know that omitting them
|
|
85
|
-
produces a bodyless 502.
|
|
86
|
-
|
|
87
|
-
`--dry-run` prints the intent that would be sent without sending it — also the starting point if
|
|
88
|
-
you need a multi-leg intent, which you then pass to `tx create --intent @file.json`.
|
|
89
|
-
|
|
90
|
-
## Transfers
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
certen tx create --identity <uuid> --to-chain ethereum-sepolia \
|
|
94
|
-
--from 0xYourAbstractAccount --to 0xRecipient --amount 0.001 --sign-with dev --wait
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**`--amount` is in WHOLE UNITS.** `1` means one ETH, `0.5` means half. This is the field most
|
|
98
|
-
worth reading twice: the gateway documented it as wei until 2026-08-11, and someone sending `1`
|
|
99
|
-
meaning one wei moves a whole ETH — which on a funded account succeeds silently.
|
|
100
|
-
|
|
101
|
-
`--from` is the identity's abstract account on the source chain; `certen portfolio` shows it.
|
|
102
|
-
|
|
103
|
-
If that account has no gas, the CLI refuses before submitting. That refusal is worth having: an
|
|
104
|
-
intent from an empty abstract account is accepted, signed and submitted — every step reports
|
|
105
|
-
success — and then parks at `anchoring` forever, because the execution leg cannot run on chain.
|
|
106
|
-
`--force` overrides it.
|
|
107
|
-
|
|
108
|
-
Or drive the steps yourself — useful when the signer is an HSM, another machine, or your own
|
|
109
|
-
policy engine:
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
certen tx create --identity <uuid> ... # returns signing_data.hash_to_sign
|
|
113
|
-
certen keys sign --name dev --hash <hash> # or your HSM
|
|
114
|
-
certen tx sign <intent-id> --signature <sig> --public-key <pub>
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
`--signature`/`--public-key` remain first-class. `--sign-with` is a convenience, not a replacement.
|
|
118
|
-
|
|
119
|
-
## Proofs
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
certen proof get <intent-id> # also accepts a proof id or a transaction hash
|
|
123
|
-
certen proof bundle <proof-id> # the artifact to hand over
|
|
124
|
-
certen proof share <proof-id> # a link a counterparty opens without a key of yours
|
|
125
|
-
certen proof verify <intent-id> # what was, and was NOT, verified
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
`proof verify` reports three separate judgements — inclusion, authorization, outcome — and it can
|
|
129
|
-
establish only the first, and only as something the gateway asserted. It says so. Asking the
|
|
130
|
-
gateway is not independent verification; to verify without trusting CERTEN, query an Accumulate
|
|
131
|
-
node for the receipt and read the execution on the destination chain.
|
|
132
|
-
|
|
133
|
-
`proof get` falls back to the Accumulate merkle receipt when the proof-service is unavailable or
|
|
134
|
-
when an intent has no `proof_id` — the normal case for governance and authorization transactions.
|
|
135
|
-
A 5xx from the proof-service means that service is down, **not** that your proof is missing.
|
|
136
|
-
|
|
137
|
-
## Multi-party approvals
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
certen pending list
|
|
141
|
-
certen pending sign <id> --identity <adi> --vote approve
|
|
142
|
-
certen pending submit <request-id> --sign-with dev --hash <hash>
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
`--vote` takes `approve`, `reject`, or `abstain` — lowercase strings. Not `accept`, and not a
|
|
146
|
-
number.
|
|
147
|
-
|
|
148
|
-
## Everything else
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
certen chains # what CERTEN is deployed on (no API key needed)
|
|
152
|
-
certen whoami # which key, which gateway, what standing
|
|
153
|
-
certen identity get <id> | list | link-chain <id> --chain <chain> | retire <id> --yes
|
|
154
|
-
certen portfolio # balances across every identity and chain
|
|
155
|
-
certen tx status <id> --wait | tx list
|
|
156
|
-
certen pricing # everything CERTEN charges for, in one call
|
|
157
|
-
certen balance | quote --chain <chain> [--sku <sku>] | fund <amount> --chain <chain>
|
|
158
|
-
certen governance add-delegate | set-threshold
|
|
159
|
-
certen admin api-keys list | create | rotate | revoke
|
|
160
|
-
certen admin audit-log | usage
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Run `certen <group> --help` for the flags on any of them, or `certen --help` for the whole tree
|
|
164
|
-
grouped by where you are in the journey.
|
|
165
|
-
|
|
166
|
-
## Chains
|
|
167
|
-
|
|
168
|
-
This CLI targets `ethereum-sepolia`, `base-sepolia` and `arbitrum-sepolia`. A chain outside that
|
|
169
|
-
set is refused with the reason — and if the gateway genuinely serves it, the refusal says so
|
|
170
|
-
rather than claiming it does not exist. `CERTEN_ALLOW_ANY_CHAIN=1` lifts the restriction.
|
|
171
|
-
|
|
172
|
-
## Scripting and AI agents: `--json`
|
|
173
|
-
|
|
174
|
-
`--json` turns the CLI into a machine interface. It is a contract, documented in full in
|
|
175
|
-
[docs/CLI-CONTRACT.md](../../docs/CLI-CONTRACT.md) and enforced by a conformance suite.
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
certen --json tx status <id>
|
|
179
|
-
# {"ok":true,"data":{"intent_id":"…","status":"completed"}}
|
|
180
|
-
|
|
181
|
-
certen --json portfolio
|
|
182
|
-
# {"ok":false,"error":{"code":"NETWORK_ERROR","message":"connect ECONNREFUSED","retryable":true,"status":0}}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
- **Exactly one JSON object on stdout**, nothing else. Every human-facing line goes to stderr.
|
|
186
|
-
- **Exit codes:** `0` ok · `1` operation failed · `2` usage error · `3` gateway unreachable. Branch on
|
|
187
|
-
these instead of parsing text. `3` guarantees nothing was submitted, so a retry cannot
|
|
188
|
-
double-execute.
|
|
189
|
-
- **`error.retryable`** comes from the SDK's own `CertenError.isRetryable`, so the CLI and the SDK
|
|
190
|
-
give an identical retry decision.
|
|
191
|
-
- **`certen --help --json`** returns the entire command tree — every command, flag and exit code — in
|
|
192
|
-
one call.
|
|
193
|
-
|
|
194
|
-
Without `--json`, output is the human table format as before. Do not parse it.
|
|
195
|
-
|
|
196
|
-
## Configuration
|
|
197
|
-
|
|
198
|
-
| | |
|
|
199
|
-
|---|---|
|
|
200
|
-
| `CERTEN_API_KEY` | API key. Always wins, so CI never touches the keyring or config file. |
|
|
201
|
-
| `CERTEN_API_URL` | Gateway base URL. Defaults to `https://gateway.kompendium.co`. |
|
|
202
|
-
| `CERTEN_KEY_PASSPHRASE` | Passphrase for local signing keys. |
|
|
203
|
-
|
|
204
|
-
`certen auth login` stores the API key in your OS keyring by default, or in
|
|
205
|
-
`~/.certen/config.json` at `0600` with `--no-keyring`.
|
|
206
|
-
|
|
207
|
-
## Things that will bite you
|
|
208
|
-
|
|
209
|
-
**Identity creation is asynchronous.** `identity create` returns `202` and provisioning continues.
|
|
210
|
-
Poll until the status is terminal, and check `can_sign` — it derives from the on-chain key page, so
|
|
211
|
-
it can read `true` while the status is still `creating`.
|
|
212
|
-
|
|
213
|
-
**A proof cycle takes 60–110 seconds.** Real validator work, not a tunable delay. Do not wrap it in
|
|
214
|
-
a 30-second timeout.
|
|
215
|
-
|
|
216
|
-
**Sign the bytes, not the text.** If you are producing signatures outside this CLI: sign the raw
|
|
217
|
-
bytes of the hash, do not hash it again, and do not sign the ASCII of the hex string. All three
|
|
218
|
-
mistakes produce a well-formed 128-hex signature the gateway rejects. `certen keys sign` handles
|
|
219
|
-
this for you.
|
|
220
|
-
|
|
221
|
-
## Documentation
|
|
222
|
-
|
|
223
|
-
**<https://docs.kompendium.co>** — getting started, authentication, errors, idempotency, and
|
|
224
|
-
task-shaped guides: onboarding an identity, external signing, proof-gating a contract call, M-of-N
|
|
225
|
-
panels, and verifying a proof.
|
|
226
|
-
|
|
227
|
-
The [live API reference](https://gateway.kompendium.co/reference) is generated from the running
|
|
228
|
-
gateway and is authoritative — when a guide and the spec disagree, the spec is right.
|
|
229
|
-
|
|
230
|
-
## License
|
|
231
|
-
|
|
232
|
-
MIT
|
|
1
|
+
# `@certen.io/cli`
|
|
2
|
+
|
|
3
|
+
The `certen` command line for the [CERTEN Gateway](https://gateway.kompendium.co/reference) —
|
|
4
|
+
proof-gated cross-chain execution on Accumulate.
|
|
5
|
+
|
|
6
|
+
**Docs: <https://docs.kompendium.co>** · **Portal: <https://gateway.kompendium.co/portal>**
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g @certen.io/cli
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## From nothing to a proof
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
certen login # approve this machine once in the portal; the key arrives here
|
|
16
|
+
certen init # signing key, identity, funding — created and checked
|
|
17
|
+
|
|
18
|
+
certen call --identity <id> --chain base-sepolia --to 0xYourContract \
|
|
19
|
+
--fn 'confirm(bytes32)' --arg 0x… --sign-with dev --wait
|
|
20
|
+
|
|
21
|
+
certen proof get <intent-id> # the evidence, to hand to a counterparty
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**You never copy an API key.** `certen login` uses the device authorization grant: it prints a
|
|
25
|
+
short code, you approve that code in a portal session you already trust, and the CLI collects the
|
|
26
|
+
key over its own channel. The secret is never displayed here and never passes through your
|
|
27
|
+
clipboard or your shell history.
|
|
28
|
+
|
|
29
|
+
`certen init` is idempotent. It creates only what is missing, records the identity id so a later
|
|
30
|
+
run reuses it, waits until the identity can actually sign, and tells you if an abstract account
|
|
31
|
+
needs gas before it can execute anything.
|
|
32
|
+
|
|
33
|
+
Stuck at any point:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
certen doctor # names the one thing blocking you, and the command that fixes it
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### If your gateway predates device authorization
|
|
40
|
+
|
|
41
|
+
`certen login` will say so and point at the portal. Mint a key there and hand it over without
|
|
42
|
+
putting it in your shell history:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
certen auth login --api-key - # reads the key from stdin
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Your key never leaves this machine
|
|
49
|
+
|
|
50
|
+
`certen keys generate` creates an Ed25519 key, encrypts it with a passphrase (scrypt + AES-256-GCM),
|
|
51
|
+
and writes it to `~/.certen/keys/<name>.json` with `0600` permissions. The CLI sends **signatures**
|
|
52
|
+
to the gateway. It never sends a private key, and there is no code path that could.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
certen keys generate --name dev # prompts for a passphrase (confirmed)
|
|
56
|
+
certen keys generate --name ci --no-passphrase # unencrypted; file permissions only
|
|
57
|
+
certen keys list # metadata only — never decrypts
|
|
58
|
+
certen keys show dev
|
|
59
|
+
certen keys verify dev # proves the key decrypts and signs correctly
|
|
60
|
+
certen keys sign --name dev --hash <hex> # print a signature, send nothing
|
|
61
|
+
certen keys delete dev --yes
|
|
62
|
+
certen keys path
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Set `CERTEN_KEY_PASSPHRASE` to skip the prompt in CI. When it is set and there is no TTY, the CLI
|
|
66
|
+
uses it; when neither is available it fails with an explanation rather than hanging on a prompt
|
|
67
|
+
nobody can see.
|
|
68
|
+
|
|
69
|
+
**`certen keys sign` sends nothing anywhere.** It is the air-gapped path: generate on one machine,
|
|
70
|
+
carry the hash to it, carry the signature back.
|
|
71
|
+
|
|
72
|
+
## Proof-gated contract calls
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
certen call --identity <uuid> --chain base-sepolia --to 0xTarget \
|
|
76
|
+
--fn 'confirm(bytes32)' --arg 0x… --sign-with dev --wait
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`--fn` takes a Solidity signature and `--arg` repeats positionally. Arguments are checked against
|
|
80
|
+
the signature **before** anything is sent — on a proof-gated call, a valid proof of the *wrong*
|
|
81
|
+
call is still a valid proof, so a mis-encoded argument is worse than an error.
|
|
82
|
+
|
|
83
|
+
The ADI URL, the abstract account (`msg.sender` on chain) and the numeric chain id are all derived
|
|
84
|
+
from the identity. You do not supply them, and you should not need to know that omitting them
|
|
85
|
+
produces a bodyless 502.
|
|
86
|
+
|
|
87
|
+
`--dry-run` prints the intent that would be sent without sending it — also the starting point if
|
|
88
|
+
you need a multi-leg intent, which you then pass to `tx create --intent @file.json`.
|
|
89
|
+
|
|
90
|
+
## Transfers
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
certen tx create --identity <uuid> --to-chain ethereum-sepolia \
|
|
94
|
+
--from 0xYourAbstractAccount --to 0xRecipient --amount 0.001 --sign-with dev --wait
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**`--amount` is in WHOLE UNITS.** `1` means one ETH, `0.5` means half. This is the field most
|
|
98
|
+
worth reading twice: the gateway documented it as wei until 2026-08-11, and someone sending `1`
|
|
99
|
+
meaning one wei moves a whole ETH — which on a funded account succeeds silently.
|
|
100
|
+
|
|
101
|
+
`--from` is the identity's abstract account on the source chain; `certen portfolio` shows it.
|
|
102
|
+
|
|
103
|
+
If that account has no gas, the CLI refuses before submitting. That refusal is worth having: an
|
|
104
|
+
intent from an empty abstract account is accepted, signed and submitted — every step reports
|
|
105
|
+
success — and then parks at `anchoring` forever, because the execution leg cannot run on chain.
|
|
106
|
+
`--force` overrides it.
|
|
107
|
+
|
|
108
|
+
Or drive the steps yourself — useful when the signer is an HSM, another machine, or your own
|
|
109
|
+
policy engine:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
certen tx create --identity <uuid> ... # returns signing_data.hash_to_sign
|
|
113
|
+
certen keys sign --name dev --hash <hash> # or your HSM
|
|
114
|
+
certen tx sign <intent-id> --signature <sig> --public-key <pub>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`--signature`/`--public-key` remain first-class. `--sign-with` is a convenience, not a replacement.
|
|
118
|
+
|
|
119
|
+
## Proofs
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
certen proof get <intent-id> # also accepts a proof id or a transaction hash
|
|
123
|
+
certen proof bundle <proof-id> # the artifact to hand over
|
|
124
|
+
certen proof share <proof-id> # a link a counterparty opens without a key of yours
|
|
125
|
+
certen proof verify <intent-id> # what was, and was NOT, verified
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`proof verify` reports three separate judgements — inclusion, authorization, outcome — and it can
|
|
129
|
+
establish only the first, and only as something the gateway asserted. It says so. Asking the
|
|
130
|
+
gateway is not independent verification; to verify without trusting CERTEN, query an Accumulate
|
|
131
|
+
node for the receipt and read the execution on the destination chain.
|
|
132
|
+
|
|
133
|
+
`proof get` falls back to the Accumulate merkle receipt when the proof-service is unavailable or
|
|
134
|
+
when an intent has no `proof_id` — the normal case for governance and authorization transactions.
|
|
135
|
+
A 5xx from the proof-service means that service is down, **not** that your proof is missing.
|
|
136
|
+
|
|
137
|
+
## Multi-party approvals
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
certen pending list
|
|
141
|
+
certen pending sign <id> --identity <adi> --vote approve
|
|
142
|
+
certen pending submit <request-id> --sign-with dev --hash <hash>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`--vote` takes `approve`, `reject`, or `abstain` — lowercase strings. Not `accept`, and not a
|
|
146
|
+
number.
|
|
147
|
+
|
|
148
|
+
## Everything else
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
certen chains # what CERTEN is deployed on (no API key needed)
|
|
152
|
+
certen whoami # which key, which gateway, what standing
|
|
153
|
+
certen identity get <id> | list | link-chain <id> --chain <chain> | retire <id> --yes
|
|
154
|
+
certen portfolio # balances across every identity and chain
|
|
155
|
+
certen tx status <id> --wait | tx list
|
|
156
|
+
certen pricing # everything CERTEN charges for, in one call
|
|
157
|
+
certen balance | quote --chain <chain> [--sku <sku>] | fund <amount> --chain <chain>
|
|
158
|
+
certen governance add-delegate | set-threshold
|
|
159
|
+
certen admin api-keys list | create | rotate | revoke
|
|
160
|
+
certen admin audit-log | usage
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Run `certen <group> --help` for the flags on any of them, or `certen --help` for the whole tree
|
|
164
|
+
grouped by where you are in the journey.
|
|
165
|
+
|
|
166
|
+
## Chains
|
|
167
|
+
|
|
168
|
+
This CLI targets `ethereum-sepolia`, `base-sepolia` and `arbitrum-sepolia`. A chain outside that
|
|
169
|
+
set is refused with the reason — and if the gateway genuinely serves it, the refusal says so
|
|
170
|
+
rather than claiming it does not exist. `CERTEN_ALLOW_ANY_CHAIN=1` lifts the restriction.
|
|
171
|
+
|
|
172
|
+
## Scripting and AI agents: `--json`
|
|
173
|
+
|
|
174
|
+
`--json` turns the CLI into a machine interface. It is a contract, documented in full in
|
|
175
|
+
[docs/CLI-CONTRACT.md](../../docs/CLI-CONTRACT.md) and enforced by a conformance suite.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
certen --json tx status <id>
|
|
179
|
+
# {"ok":true,"data":{"intent_id":"…","status":"completed"}}
|
|
180
|
+
|
|
181
|
+
certen --json portfolio
|
|
182
|
+
# {"ok":false,"error":{"code":"NETWORK_ERROR","message":"connect ECONNREFUSED","retryable":true,"status":0}}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- **Exactly one JSON object on stdout**, nothing else. Every human-facing line goes to stderr.
|
|
186
|
+
- **Exit codes:** `0` ok · `1` operation failed · `2` usage error · `3` gateway unreachable. Branch on
|
|
187
|
+
these instead of parsing text. `3` guarantees nothing was submitted, so a retry cannot
|
|
188
|
+
double-execute.
|
|
189
|
+
- **`error.retryable`** comes from the SDK's own `CertenError.isRetryable`, so the CLI and the SDK
|
|
190
|
+
give an identical retry decision.
|
|
191
|
+
- **`certen --help --json`** returns the entire command tree — every command, flag and exit code — in
|
|
192
|
+
one call.
|
|
193
|
+
|
|
194
|
+
Without `--json`, output is the human table format as before. Do not parse it.
|
|
195
|
+
|
|
196
|
+
## Configuration
|
|
197
|
+
|
|
198
|
+
| | |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `CERTEN_API_KEY` | API key. Always wins, so CI never touches the keyring or config file. |
|
|
201
|
+
| `CERTEN_API_URL` | Gateway base URL. Defaults to `https://gateway.kompendium.co`. |
|
|
202
|
+
| `CERTEN_KEY_PASSPHRASE` | Passphrase for local signing keys. |
|
|
203
|
+
|
|
204
|
+
`certen auth login` stores the API key in your OS keyring by default, or in
|
|
205
|
+
`~/.certen/config.json` at `0600` with `--no-keyring`.
|
|
206
|
+
|
|
207
|
+
## Things that will bite you
|
|
208
|
+
|
|
209
|
+
**Identity creation is asynchronous.** `identity create` returns `202` and provisioning continues.
|
|
210
|
+
Poll until the status is terminal, and check `can_sign` — it derives from the on-chain key page, so
|
|
211
|
+
it can read `true` while the status is still `creating`.
|
|
212
|
+
|
|
213
|
+
**A proof cycle takes 60–110 seconds.** Real validator work, not a tunable delay. Do not wrap it in
|
|
214
|
+
a 30-second timeout.
|
|
215
|
+
|
|
216
|
+
**Sign the bytes, not the text.** If you are producing signatures outside this CLI: sign the raw
|
|
217
|
+
bytes of the hash, do not hash it again, and do not sign the ASCII of the hex string. All three
|
|
218
|
+
mistakes produce a well-formed 128-hex signature the gateway rejects. `certen keys sign` handles
|
|
219
|
+
this for you.
|
|
220
|
+
|
|
221
|
+
## Documentation
|
|
222
|
+
|
|
223
|
+
**<https://docs.kompendium.co>** — getting started, authentication, errors, idempotency, and
|
|
224
|
+
task-shaped guides: onboarding an identity, external signing, proof-gating a contract call, M-of-N
|
|
225
|
+
panels, and verifying a proof.
|
|
226
|
+
|
|
227
|
+
The [live API reference](https://gateway.kompendium.co/reference) is generated from the running
|
|
228
|
+
gateway and is authoritative — when a guide and the spec disagree, the spec is right.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT
|
package/dist/commands/chains.js
CHANGED
|
@@ -116,8 +116,8 @@ export function registerChainsCommands(program) {
|
|
|
116
116
|
hint('');
|
|
117
117
|
hint('Detail for one: certen chains base-sepolia');
|
|
118
118
|
});
|
|
119
|
-
chains.addHelpText('after', `
|
|
120
|
-
Registry version and Accumulate network are included in --json output.
|
|
119
|
+
chains.addHelpText('after', `
|
|
120
|
+
Registry version and Accumulate network are included in --json output.
|
|
121
121
|
This command needs no API key, so it also answers "is the gateway up".`);
|
|
122
122
|
}
|
|
123
123
|
//# sourceMappingURL=chains.js.map
|
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Render a transaction memo for a terminal.
|
|
4
|
+
*
|
|
5
|
+
* The memo is the only field that says what a pending transaction is FOR, and it matters most for
|
|
6
|
+
* the ones the signer did not create — an authority transaction is pending on someone else's
|
|
7
|
+
* account, so without it the inbox offers only `writeData` and an unfamiliar URL.
|
|
8
|
+
*
|
|
9
|
+
* It is also written by whoever built that transaction, who is NOT the person being asked to sign.
|
|
10
|
+
* So it is sanitised before it reaches a terminal:
|
|
11
|
+
*
|
|
12
|
+
* - ANSI/control characters are stripped. A memo carrying escape codes could otherwise repaint the
|
|
13
|
+
* line, hide text, or forge output that looks like it came from the CLI.
|
|
14
|
+
* - Newlines collapse to spaces, so one row cannot masquerade as several.
|
|
15
|
+
* - Truncated, so a long memo cannot push the rest of the inbox off screen.
|
|
16
|
+
*
|
|
17
|
+
* A missing memo is reported as missing rather than blank — "no memo" is information: it means
|
|
18
|
+
* nobody said why this needs signing.
|
|
19
|
+
*/
|
|
20
|
+
export declare function describeMemo(memo: string | null | undefined): string;
|
|
2
21
|
export declare function registerPendingCommands(program: Command): void;
|
package/dist/commands/pending.js
CHANGED
|
@@ -1,10 +1,38 @@
|
|
|
1
|
-
import { CertenClient } from '@certen.io/sdk';
|
|
1
|
+
import { CertenClient, resolveSignTarget } from '@certen.io/sdk';
|
|
2
2
|
import { getApiKey, getApiUrl } from '../config.js';
|
|
3
|
-
import { printOutput, hint, isJsonMode } from '../output.js';
|
|
3
|
+
import { printOutput, hint, human, isJsonMode } from '../output.js';
|
|
4
4
|
import { resolveSignature } from '../signer.js';
|
|
5
|
+
import { UsageError } from '../errors.js';
|
|
5
6
|
async function getClient() {
|
|
6
7
|
return new CertenClient({ apiKey: await getApiKey(), baseUrl: getApiUrl() });
|
|
7
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Render a transaction memo for a terminal.
|
|
11
|
+
*
|
|
12
|
+
* The memo is the only field that says what a pending transaction is FOR, and it matters most for
|
|
13
|
+
* the ones the signer did not create — an authority transaction is pending on someone else's
|
|
14
|
+
* account, so without it the inbox offers only `writeData` and an unfamiliar URL.
|
|
15
|
+
*
|
|
16
|
+
* It is also written by whoever built that transaction, who is NOT the person being asked to sign.
|
|
17
|
+
* So it is sanitised before it reaches a terminal:
|
|
18
|
+
*
|
|
19
|
+
* - ANSI/control characters are stripped. A memo carrying escape codes could otherwise repaint the
|
|
20
|
+
* line, hide text, or forge output that looks like it came from the CLI.
|
|
21
|
+
* - Newlines collapse to spaces, so one row cannot masquerade as several.
|
|
22
|
+
* - Truncated, so a long memo cannot push the rest of the inbox off screen.
|
|
23
|
+
*
|
|
24
|
+
* A missing memo is reported as missing rather than blank — "no memo" is information: it means
|
|
25
|
+
* nobody said why this needs signing.
|
|
26
|
+
*/
|
|
27
|
+
export function describeMemo(memo) {
|
|
28
|
+
if (!memo)
|
|
29
|
+
return 'memo: (none given)';
|
|
30
|
+
// eslint-disable-next-line no-control-regex
|
|
31
|
+
const clean = memo.replace(/[\u0000-\u001F\u007F-\u009F]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
32
|
+
if (!clean)
|
|
33
|
+
return 'memo: (none given)';
|
|
34
|
+
return `memo: ${clean.length > 120 ? `${clean.slice(0, 117)}...` : clean}`;
|
|
35
|
+
}
|
|
8
36
|
export function registerPendingCommands(program) {
|
|
9
37
|
const pending = program.command('pending').description('Pending actions inbox');
|
|
10
38
|
pending
|
|
@@ -24,25 +52,81 @@ export function registerPendingCommands(program) {
|
|
|
24
52
|
limit: opts.limit,
|
|
25
53
|
offset: opts.offset,
|
|
26
54
|
});
|
|
27
|
-
|
|
55
|
+
// machineOnly: the generic table flattens `actions[]` into something unreadable, and the
|
|
56
|
+
// memo — the one field that says what each item IS — would be lost in it.
|
|
57
|
+
printOutput(result, { machineOnly: true });
|
|
58
|
+
if (isJsonMode())
|
|
59
|
+
return;
|
|
60
|
+
const actions = result.actions ?? [];
|
|
61
|
+
if (actions.length === 0) {
|
|
62
|
+
human('(nothing pending)');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const a of actions) {
|
|
66
|
+
// Principal before type: for an authority transaction it is an account the signer does
|
|
67
|
+
// NOT own, and that is the thing worth noticing.
|
|
68
|
+
human(`${a.id} ${a.type ?? '?'} ${a.principal ?? ''}`);
|
|
69
|
+
human(` ${describeMemo(a.memo)}`);
|
|
70
|
+
human(` status=${a.status ?? '?'} signed=${a.user_has_signed ? 'yes' : 'no'}`
|
|
71
|
+
+ (a.expires_at ? ` expires=${a.expires_at}` : ''));
|
|
72
|
+
}
|
|
28
73
|
});
|
|
29
74
|
pending
|
|
30
|
-
.command('sign <
|
|
31
|
-
.description('Create a sign request for a pending action')
|
|
32
|
-
.option('--identity <id>', 'Identity to sign as')
|
|
33
|
-
.option('--signer-url <url>', 'Signer URL')
|
|
75
|
+
.command('sign <target>')
|
|
76
|
+
.description('Create a sign request for a pending action (inbox id) or a pending transaction (hash or TxID)')
|
|
77
|
+
.option('--identity <id>', 'Identity to sign as (required with a transaction hash)')
|
|
78
|
+
.option('--signer-url <url>', 'Signer URL (required with a transaction hash)')
|
|
79
|
+
.option('--public-key <hex>', 'Public key that will sign, 64-char hex (required with a transaction hash)')
|
|
34
80
|
// Was documented as "accept/reject". The API takes a lowercase `approve` | `reject` |
|
|
35
81
|
// `abstain`; `accept` is rejected. The help text was sending people to a value that fails.
|
|
36
82
|
.option('--vote <vote>', 'Vote: approve | reject | abstain')
|
|
37
|
-
.action(async (
|
|
83
|
+
.action(async (rawTarget, opts) => {
|
|
84
|
+
// Inferred from the argument, not from a flag: an inbox id is a UUID and a transaction is a
|
|
85
|
+
// 64-hex hash, so the two are disjoint and a --type flag would only be a new way to get it
|
|
86
|
+
// wrong. Resolution happens BEFORE the client is built, so a bad target never opens a
|
|
87
|
+
// connection or reads a key.
|
|
88
|
+
let target;
|
|
89
|
+
try {
|
|
90
|
+
target = resolveSignTarget(rawTarget);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
throw new UsageError(err.message, 'INVALID_SIGN_TARGET');
|
|
94
|
+
}
|
|
95
|
+
let params;
|
|
96
|
+
if (target.type === 'pending_tx') {
|
|
97
|
+
// A raw transaction has no inbox row behind it, so nothing derives these. Fail here with
|
|
98
|
+
// the CLI's own usage error rather than letting the gateway answer 400 — the caller can
|
|
99
|
+
// tell "you left a flag out" from "the gateway refused" only if we say so.
|
|
100
|
+
const missing = [
|
|
101
|
+
['identity', opts.identity],
|
|
102
|
+
['signer-url', opts.signerUrl],
|
|
103
|
+
['public-key', opts.publicKey],
|
|
104
|
+
].filter(([, v]) => !v).map(([flag]) => flag);
|
|
105
|
+
if (missing.length > 0) {
|
|
106
|
+
throw new UsageError(`signing by transaction hash requires --${missing.join(', --')}. `
|
|
107
|
+
+ 'An inbox id (UUID) derives these automatically; a raw transaction does not.', 'MISSING_SIGNER_DETAILS');
|
|
108
|
+
}
|
|
109
|
+
params = {
|
|
110
|
+
type: 'pending_tx',
|
|
111
|
+
targetId: target.targetId,
|
|
112
|
+
identity: opts.identity,
|
|
113
|
+
signerUrl: opts.signerUrl,
|
|
114
|
+
publicKey: opts.publicKey,
|
|
115
|
+
vote: opts.vote,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
params = {
|
|
120
|
+
type: 'pending_action',
|
|
121
|
+
targetId: target.targetId,
|
|
122
|
+
identity: opts.identity,
|
|
123
|
+
signerUrl: opts.signerUrl,
|
|
124
|
+
publicKey: opts.publicKey,
|
|
125
|
+
vote: opts.vote,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
38
128
|
const client = await getClient();
|
|
39
|
-
const result = await client.sign.create(
|
|
40
|
-
type: 'pending_action',
|
|
41
|
-
targetId: id,
|
|
42
|
-
identity: opts.identity,
|
|
43
|
-
signerUrl: opts.signerUrl,
|
|
44
|
-
vote: opts.vote,
|
|
45
|
-
});
|
|
129
|
+
const result = await client.sign.create(params);
|
|
46
130
|
printOutput(result);
|
|
47
131
|
if (isJsonMode())
|
|
48
132
|
return;
|
|
@@ -55,6 +139,12 @@ export function registerPendingCommands(program) {
|
|
|
55
139
|
hint('');
|
|
56
140
|
hint('This opened a sign request — the vote is not cast until the signature is submitted:');
|
|
57
141
|
hint(` certen pending submit ${requestId} --sign-with <key> --hash ${hash}`);
|
|
142
|
+
if (target.type === 'pending_tx') {
|
|
143
|
+
// The signing data was computed FOR the key named by --public-key, and the vote is folded
|
|
144
|
+
// into the preimage. A signature from any other key verifies against nothing, which fails
|
|
145
|
+
// silently — the transaction simply stays pending.
|
|
146
|
+
hint(` (sign with the key for --public-key ${opts.publicKey}; no other key fits this request)`);
|
|
147
|
+
}
|
|
58
148
|
}
|
|
59
149
|
});
|
|
60
150
|
pending
|