From ea826ab6ad8bfaabf19c4376afa811a3580df864 Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 16:13:39 +0200 Subject: [PATCH 01/13] feat(cli): audit subcommand for station configuration sanity checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `orbit-cli audit --station ` for read-only sanity checks against a station's configuration. The command pulls live state via the existing `list_*` query methods and runs static checks against the configuration. Ships three checks in this first release, each with a stable check id and severity: - `quorum.empty-approver-set` (BLOCKER) — Quorum / QuorumPercentage rules whose UserSpecifier currently resolves to zero active users; the evaluator's clamp to total_possible_approvers makes such a rule auto- approve with no votes cast. - `external-call.validation-equals-execution` (BLOCKER) — sweeps request policies and permissions for CallExternalCanister resource targets where validation_method == execution_method; the validation hook runs before approval, so a side-effecting matching pair bypasses the gate. - `asset.edit-policy-weaker-than-transfer` (WARNING) — flags when the easiest EditAsset path is strictly weaker than the strictest Transfer policy. Asset routing (ledger_canister_id) is resolved live at execute time, so a successful EditAsset between approval and execution can redirect funds. The command is read-only by construction — only query methods are called. Exit codes follow standard semantics (0 clean, 1 warning, 2 blocker) so CI pipelines can gate on it directly. `--output ` writes the report to a file instead of stdout. See `cli/src/audit/README.md` for usage, the check catalogue, and notes for adding new checks. --- cli/package.json | 4 +- cli/src/audit/README.md | 146 ++++++++++++++++++ ...t-edit-policy-weaker-than-transfer.spec.ts | 72 +++++++++ .../asset-edit-policy-weaker-than-transfer.ts | 49 ++++++ cli/src/audit/checks/describe.ts | 26 ++++ ...l-call-validation-equals-execution.spec.ts | 104 +++++++++++++ ...ternal-call-validation-equals-execution.ts | 67 ++++++++ cli/src/audit/checks/fixtures.ts | 67 ++++++++ .../checks/quorum-empty-approver-set.spec.ts | 118 ++++++++++++++ .../audit/checks/quorum-empty-approver-set.ts | 39 +++++ cli/src/audit/index.ts | 85 ++++++++++ cli/src/audit/report.ts | 85 ++++++++++ cli/src/audit/resolver.ts | 142 +++++++++++++++++ cli/src/audit/station.core.ts | 121 +++++++++++++++ cli/src/audit/types.ts | 146 ++++++++++++++++++ cli/src/cli.ts | 2 + pnpm-lock.yaml | 3 + 17 files changed, 1275 insertions(+), 1 deletion(-) create mode 100644 cli/src/audit/README.md create mode 100644 cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.spec.ts create mode 100644 cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.ts create mode 100644 cli/src/audit/checks/describe.ts create mode 100644 cli/src/audit/checks/external-call-validation-equals-execution.spec.ts create mode 100644 cli/src/audit/checks/external-call-validation-equals-execution.ts create mode 100644 cli/src/audit/checks/fixtures.ts create mode 100644 cli/src/audit/checks/quorum-empty-approver-set.spec.ts create mode 100644 cli/src/audit/checks/quorum-empty-approver-set.ts create mode 100644 cli/src/audit/index.ts create mode 100644 cli/src/audit/report.ts create mode 100644 cli/src/audit/resolver.ts create mode 100644 cli/src/audit/station.core.ts create mode 100644 cli/src/audit/types.ts diff --git a/cli/package.json b/cli/package.json index 4759ef9fc..b220f106e 100644 --- a/cli/package.json +++ b/cli/package.json @@ -9,6 +9,7 @@ "build": "tsc", "start": "node dist/cli.js", "expose": "pnpm link --global", + "test": "vitest run", "generate-types": "didc bind --target ts ../core/control-panel/api/spec.did > ./src/generated/control_panel.d.ts" }, "devDependencies": { @@ -16,6 +17,7 @@ "@dfinity/agent": "1.4.0", "@dfinity/candid": "1.4.0", "@dfinity/identity": "1.4.0", - "@dfinity/principal": "1.4.0" + "@dfinity/principal": "1.4.0", + "vitest": "1.6.1" } } diff --git a/cli/src/audit/README.md b/cli/src/audit/README.md new file mode 100644 index 000000000..3216d8532 --- /dev/null +++ b/cli/src/audit/README.md @@ -0,0 +1,146 @@ +# `orbit-cli audit` + +Read-only sanity checks against an Orbit station's configuration. The command pulls live state via the station's `list_*` query methods, runs a set of static checks against the configuration, and prints a severity-sorted report. Nothing is mutated — the audit is safe to run at any time, against any station the caller has read access to. + +## Usage + +```bash +orbit-cli audit --station [--network ] [--identity ] [--output ] +``` + +### Options + +| Flag | Default | Purpose | +| ----------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-s, --station ` | **required** | The station canister id to audit. | +| `-n, --network ` | `ic` | The network the station lives on. | +| `-i, --identity ` | `default` | The dfx identity used to call the station. Must have read access to the station's `list_*` query methods (admin-tier users have this by default). | +| `-o, --output ` | _(stdout)_ | Write the report to a file instead of stdout. Exit code is still set based on findings. | +| `-h, --help` | | Print help and exit. | + +### Exit codes + +| Code | Meaning | +| ---- | -------------------------------------- | +| `0` | No findings. | +| `1` | At least one `WARNING`, no `BLOCKER`s. | +| `2` | At least one `BLOCKER`. | + +Useful for CI integration — `set -e` pipelines fail naturally when a blocker shows up. + +### Examples + +```bash +# Audit a mainnet station, print report to stdout. +orbit-cli audit --station rrkah-fqaaa-aaaaa-aaaaq-cai + +# As an admin-tier identity, write the report to a file. +orbit-cli audit --station rrkah-fqaaa-aaaaa-aaaaq-cai \ + --identity admin-readonly \ + --output ./audit-report.txt + +# Local development station. +orbit-cli audit --station --network local +``` + +## What it checks + +The first release ships three checks. Each finding has a stable check id (so configs and CI rules can refer to them without breaking across versions) and a severity (`BLOCKER`, `WARNING`, or `INFO`). + +### `quorum.empty-approver-set` — `BLOCKER` + +Flags `Quorum` / `QuorumPercentage` rules whose `UserSpecifier` currently resolves to **zero active users**. + +`RequestApprovalSummary::evaluate` clamps `min_approved` down to the number of possible approvers. When the approver set is empty the clamp drives the threshold to `0`, and `approved (0) >= min_approved (0)` evaluates the rule to `Approved` without any votes cast. The next matching request auto-approves silently. + +The check walks every `RequestPolicy.rule`, including nested combinators (`AnyOf`, `AllOf`, `Not`) and `NamedRule` references. Cycles in `NamedRule` resolution are detected and skipped. + +### `external-call.validation-equals-execution` — `BLOCKER` + +Flags `CallExternalCanister` permissions and request policies whose `validation_method` and `execution_method` resolve to the same `(canister_id, method_name)` pair. + +When a `CallExternalCanister` request is submitted, the station invokes the configured `validation_method` immediately to render and validate the argument blob — before the approval policy has completed. If that method is also the `execution_method`, its side effect runs at submission time, bypassing the approval gate. There is no benign reason for the pair to match. + +The check sweeps both surfaces — request policies (`RequestSpecifier::CallExternalCanister`) and permissions (`Resource::ExternalCanister(Call(...))`). + +### `asset.edit-policy-weaker-than-transfer` — `WARNING` + +Warns when the **easiest** path to passing an `EditAsset` request requires fewer approvals than the **strictest** `Transfer` policy in the station. + +An approved transfer's destination ledger is resolved from `asset.metadata["ledger_canister_id"]` at execute time. A successful `EditAsset` between approval and execution can therefore redirect routing. When `EditAsset` is gated more loosely than `Transfer`, an actor who can pass the lower bar can subvert the higher one. + +The MVP compares the easiest `EditAsset` path against the strictest `Transfer` path station-wide. Per-asset / per-account scoping is a planned follow-up. + +## Report format + +The report is plain text, sorted by severity (`BLOCKER` → `WARNING` → `INFO`), with each finding showing the offending policy / asset / permission and a suggested fix. A `summary:` line ends the report with counts. + +``` +Orbit Station Audit Report +station: rrkah-fqaaa-aaaaa-aaaaq-cai +network: ic + +==== BLOCKERS (1) ==== + +[quorum.empty-approver-set] + policy abc123... (Transfer(Any)) — Quorum + Quorum rule asks for 1 approval(s) but UserSpecifier::Id(1 user(s)) currently + resolves to 0 active users. Next matching request will auto-approve. + fix: Add eligible approvers to this specifier, or wrap in AnyOf with an + admin-group fallback before the next matching request is submitted. + +==== Positive confirmations ==== +- 12 request policies loaded. +- 5 users loaded. +- ... + +summary: 1 blocker, 0 warning, 0 info +``` + +## Implementation notes + +- **No new runtime dependencies.** The audit uses the same `dfx canister call --output json` pattern as the existing `registry/` and `release/` subcommands. The result is parsed as Candid-in-JSON. +- **Hand-written types.** The audit imports a small subset of the station API as hand-written TypeScript in [types.ts](./types.ts) instead of generating bindings with `didc bind`. The surface is small enough that the extra build step isn't worth it for this MVP. If the audit grows, switching to generated bindings is a straightforward refactor. +- **Specifier resolution mirrors the station evaluator.** `resolveApprovers` and `minVotesForRule` in [resolver.ts](./resolver.ts) reimplement the relevant subset of `find_matching_users` and `RequestApprovalSummary::evaluate` from `core/station/impl/src/models/request_policy_rule.rs`. If the station evaluator changes, the resolver may need updating; the tests cover the static rule shapes used today. +- **Read-only by construction.** Only query methods are called. The audit cannot mutate state under any circumstance. + +## Tests + +```bash +pnpm --filter orbit-cli test +``` + +Unit tests cover each check across positive and negative cases, including combinator descent and cycle detection on `NamedRule` references. Fixtures are in [checks/fixtures.ts](./checks/fixtures.ts). + +The audit's check logic is exercised by these tests; the dfx-call plumbing is exercised end-to-end against a real station (see the example invocations above). + +## Extending + +Each check is a function that takes the loaded station data and returns `Finding[]`: + +```ts +export const myCheck = ( + policies: RequestPolicy[], + users: User[], + /* ... */ +): Finding[] => { + // ... +}; +``` + +To add a new check: + +1. Drop a new file in `checks/` returning `Finding[]`. Use a stable check id under a dot-namespace (`category.short-name`). +2. Spec it in `checks/.spec.ts` with at least one positive and one negative case. Reuse the fixture builders in `checks/fixtures.ts`. +3. Wire it into `index.ts` alongside the existing checks. + +A planned follow-up will move this to a registry pattern with a stable plugin interface so third-party checks can be loaded without modifying core. + +## Roadmap + +Possible follow-ups (not committed): + +- `--format json` for CI / pipeline consumption. +- Config file with per-check enable/disable + severity overrides + threshold knobs. +- Plugin loader so out-of-tree checks can be added without forking. +- Additional checks: small-group Quorum specifiers (WARNING), missing admin-group fallback (INFO), dangling user/group references (WARNING), per-asset `EditAsset` vs `Transfer` scoping (refines the current station-wide check). diff --git a/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.spec.ts b/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.spec.ts new file mode 100644 index 000000000..9ab4434e7 --- /dev/null +++ b/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.spec.ts @@ -0,0 +1,72 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import { assetEditPolicyWeakerThanTransfer } from './asset-edit-policy-weaker-than-transfer'; +import { makePolicy, makeUser, resetCounter } from './fixtures'; + +describe('asset.edit-policy-weaker-than-transfer', () => { + beforeEach(() => resetCounter()); + + const fiveActiveUsers = () => Array.from({ length: 5 }, (_, i) => makeUser({ id: `u-${i}` })); + + it('does not fire when there are no EditAsset policies', () => { + const users = fiveActiveUsers(); + const policy = makePolicy( + { Transfer: { Any: null } }, + { Quorum: { approvers: { Any: null }, min_approved: 2 } }, + ); + const findings = assetEditPolicyWeakerThanTransfer([policy], users, []); + expect(findings).toHaveLength(0); + }); + + it('does not fire when there are no Transfer policies', () => { + const users = fiveActiveUsers(); + const policy = makePolicy( + { EditAsset: { Any: null } }, + { Quorum: { approvers: { Any: null }, min_approved: 2 } }, + ); + const findings = assetEditPolicyWeakerThanTransfer([policy], users, []); + expect(findings).toHaveLength(0); + }); + + it('does not fire when EditAsset is gated as strict as the strictest Transfer', () => { + const users = fiveActiveUsers(); + const editPolicy = makePolicy( + { EditAsset: { Any: null } }, + { Quorum: { approvers: { Any: null }, min_approved: 3 } }, + ); + const transferPolicy = makePolicy( + { Transfer: { Any: null } }, + { Quorum: { approvers: { Any: null }, min_approved: 3 } }, + ); + const findings = assetEditPolicyWeakerThanTransfer([editPolicy, transferPolicy], users, []); + expect(findings).toHaveLength(0); + }); + + it('fires when EditAsset is gated more loosely than Transfer', () => { + const users = fiveActiveUsers(); + const editPolicy = makePolicy( + { EditAsset: { Any: null } }, + { Quorum: { approvers: { Any: null }, min_approved: 1 } }, + ); + const transferPolicy = makePolicy( + { Transfer: { Any: null } }, + { Quorum: { approvers: { Any: null }, min_approved: 3 } }, + ); + const findings = assetEditPolicyWeakerThanTransfer([editPolicy, transferPolicy], users, []); + expect(findings).toHaveLength(1); + expect(findings[0].severity).toBe('warning'); + expect(findings[0].message).toMatch(/Easiest EditAsset path requires 1/); + expect(findings[0].message).toMatch(/strictest Transfer path requires 3/); + }); + + it('uses minVotes including AutoApproved (treated as 0)', () => { + const users = fiveActiveUsers(); + const editPolicy = makePolicy({ EditAsset: { Any: null } }, { AutoApproved: null }); + const transferPolicy = makePolicy( + { Transfer: { Any: null } }, + { Quorum: { approvers: { Any: null }, min_approved: 2 } }, + ); + const findings = assetEditPolicyWeakerThanTransfer([editPolicy, transferPolicy], users, []); + expect(findings).toHaveLength(1); + expect(findings[0].message).toMatch(/Easiest EditAsset path requires 0/); + }); +}); diff --git a/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.ts b/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.ts new file mode 100644 index 000000000..c7168912d --- /dev/null +++ b/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.ts @@ -0,0 +1,49 @@ +import { Finding } from '../report'; +import { minVotesForRule } from '../resolver'; +import { NamedRule, RequestPolicy, User, UUID } from '../types'; + +/** + * Warns when the *easiest path* to passing an `EditAsset` request requires + * fewer approvals than the *strictest* `Transfer` policy in the station. + * + * Background: an approved transfer's destination ledger is resolved live from + * `asset.metadata["ledger_canister_id"]` at execute time. A successful + * `EditAsset` between approval and execution can therefore redirect funds. If + * `EditAsset` is gated more loosely than `Transfer`, an attacker who can pass + * the lower bar can subvert the higher one. + * + * Per-asset / per-account scoping would be more precise; the MVP compares the + * easiest EditAsset path against the strictest Transfer path across the entire + * station, which catches the typical misconfiguration without requiring an + * account-to-asset cross-reference. + */ +export const assetEditPolicyWeakerThanTransfer = ( + policies: RequestPolicy[], + users: User[], + namedRules: NamedRule[], +): Finding[] => { + const namedByUUID = new Map(namedRules.map(r => [r.id, r])); + const editPolicies = policies.filter(p => 'EditAsset' in p.specifier); + const transferPolicies = policies.filter(p => 'Transfer' in p.specifier); + + if (editPolicies.length === 0 || transferPolicies.length === 0) return []; + + const editVotes = editPolicies.map(p => minVotesForRule(p.rule, users, namedByUUID)); + const transferVotes = transferPolicies.map(p => minVotesForRule(p.rule, users, namedByUUID)); + + const easiestEdit = Math.min(...editVotes); + const strictestTransfer = Math.max(...transferVotes); + + if (!Number.isFinite(easiestEdit) || !Number.isFinite(strictestTransfer)) return []; + if (easiestEdit >= strictestTransfer) return []; + + return [ + { + checkId: 'asset.edit-policy-weaker-than-transfer', + severity: 'warning', + location: `station-wide (${editPolicies.length} EditAsset policy/policies vs ${transferPolicies.length} Transfer policy/policies)`, + message: `Easiest EditAsset path requires ${easiestEdit} approval(s); strictest Transfer path requires ${strictestTransfer}. An attacker who can pass the EditAsset bar can mutate asset.metadata["ledger_canister_id"] and redirect an approved Transfer between approval and execution.`, + fix: 'Gate EditAsset at the same approval level as Transfer for the affected assets. Until v0.2 supports per-asset scoping, treat this as a station-wide signal to tighten the loosest EditAsset policy.', + }, + ]; +}; diff --git a/cli/src/audit/checks/describe.ts b/cli/src/audit/checks/describe.ts new file mode 100644 index 000000000..07bdc208d --- /dev/null +++ b/cli/src/audit/checks/describe.ts @@ -0,0 +1,26 @@ +import { RequestSpecifier, ResourceIds, UserSpecifier } from '../types'; + +export const describeSpecifier = (specifier: UserSpecifier): string => { + if ('Any' in specifier) return 'UserSpecifier::Any'; + if ('Id' in specifier) return `UserSpecifier::Id(${specifier.Id.length} user(s))`; + if ('Group' in specifier) return `UserSpecifier::Group(${specifier.Group.length} group(s))`; + return 'UserSpecifier::?'; +}; + +const describeResourceIds = (ids: ResourceIds): string => { + if ('Any' in ids) return 'Any'; + return `[${ids.Ids.length} id(s)]`; +}; + +export const describeRequestSpecifier = (specifier: RequestSpecifier): string => { + const [variant] = Object.keys(specifier); + const value = (specifier as Record)[variant]; + if ( + value && + typeof value === 'object' && + ('Any' in (value as object) || 'Ids' in (value as object)) + ) { + return `${variant}(${describeResourceIds(value as ResourceIds)})`; + } + return variant; +}; diff --git a/cli/src/audit/checks/external-call-validation-equals-execution.spec.ts b/cli/src/audit/checks/external-call-validation-equals-execution.spec.ts new file mode 100644 index 000000000..8915c6622 --- /dev/null +++ b/cli/src/audit/checks/external-call-validation-equals-execution.spec.ts @@ -0,0 +1,104 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import { externalCallValidationEqualsExecution } from './external-call-validation-equals-execution'; +import { makePermission, makePolicy, method, resetCounter } from './fixtures'; + +describe('external-call.validation-equals-execution', () => { + beforeEach(() => resetCounter()); + + it('does not fire when validation and execution methods differ', () => { + const policy = makePolicy( + { + CallExternalCanister: { + validation_method: { + ValidationMethod: method('canister-1', 'validate_inc'), + }, + execution_method: { ExecutionMethod: method('canister-1', 'inc') }, + }, + }, + { AutoApproved: null }, + ); + const findings = externalCallValidationEqualsExecution([policy], []); + expect(findings).toHaveLength(0); + }); + + it('does not fire when validation_method is No (no hook configured)', () => { + const policy = makePolicy( + { + CallExternalCanister: { + validation_method: { No: null }, + execution_method: { ExecutionMethod: method('canister-1', 'inc') }, + }, + }, + { AutoApproved: null }, + ); + const findings = externalCallValidationEqualsExecution([policy], []); + expect(findings).toHaveLength(0); + }); + + it('fires on a request policy with matching validation and execution', () => { + const policy = makePolicy( + { + CallExternalCanister: { + validation_method: { + ValidationMethod: method('canister-1', 'inc'), + }, + execution_method: { ExecutionMethod: method('canister-1', 'inc') }, + }, + }, + { AutoApproved: null }, + ); + const findings = externalCallValidationEqualsExecution([policy], []); + expect(findings).toHaveLength(1); + expect(findings[0].severity).toBe('blocker'); + expect(findings[0].message).toMatch(/canister-1::inc/); + }); + + it('fires on a permission with matching validation and execution', () => { + const permission = makePermission({ + ExternalCanister: { + Call: { + validation_method: { + ValidationMethod: method('canister-7', 'transfer'), + }, + execution_method: { + ExecutionMethod: method('canister-7', 'transfer'), + }, + }, + }, + }); + const findings = externalCallValidationEqualsExecution([], [permission]); + expect(findings).toHaveLength(1); + expect(findings[0].severity).toBe('blocker'); + expect(findings[0].message).toMatch(/canister-7::transfer/); + }); + + it('does not flag a permission targeting different methods on same canister', () => { + const permission = makePermission({ + ExternalCanister: { + Call: { + validation_method: { + ValidationMethod: method('canister-1', 'render_args'), + }, + execution_method: { ExecutionMethod: method('canister-1', 'inc') }, + }, + }, + }); + const findings = externalCallValidationEqualsExecution([], [permission]); + expect(findings).toHaveLength(0); + }); + + it('does not flag a permission targeting same method on different canisters', () => { + const permission = makePermission({ + ExternalCanister: { + Call: { + validation_method: { + ValidationMethod: method('canister-A', 'inc'), + }, + execution_method: { ExecutionMethod: method('canister-B', 'inc') }, + }, + }, + }); + const findings = externalCallValidationEqualsExecution([], [permission]); + expect(findings).toHaveLength(0); + }); +}); diff --git a/cli/src/audit/checks/external-call-validation-equals-execution.ts b/cli/src/audit/checks/external-call-validation-equals-execution.ts new file mode 100644 index 000000000..ee83e4559 --- /dev/null +++ b/cli/src/audit/checks/external-call-validation-equals-execution.ts @@ -0,0 +1,67 @@ +import { Finding } from '../report'; +import { CallExternalCanisterResourceTarget, Permission, RequestPolicy } from '../types'; + +/** + * Flags `CallExternalCanister` resources whose `validation_method` and + * `execution_method` resolve to the same canister + method pair. + * + * Background: when a `CallExternalCanister` request is submitted, the station + * invokes the configured `validation_method` immediately to render/validate the + * argument blob — before the approval policy completes. If that method is also + * the `execution_method`, its side effect runs at submission time, bypassing + * the approval gate. There is no benign reason for the pair to match. + * + * Sweeps both surfaces: request-policy specifiers and permission resources. + */ +export const externalCallValidationEqualsExecution = ( + policies: RequestPolicy[], + permissions: Permission[], +): Finding[] => { + const findings: Finding[] = []; + + for (const policy of policies) { + if ('CallExternalCanister' in policy.specifier) { + const conflict = pairConflict(policy.specifier.CallExternalCanister); + if (conflict) { + findings.push({ + checkId: 'external-call.validation-equals-execution', + severity: 'blocker', + location: `policy ${policy.id}`, + message: `CallExternalCanister policy has validation_method == execution_method (${conflict}). Validation hook runs before approval; if the method has side effects, the approval gate is bypassed.`, + fix: 'Remove the policy, or configure the validation hook as a separate read-only method.', + }); + } + } + } + + for (const permission of permissions) { + if ( + 'ExternalCanister' in permission.resource && + 'Call' in permission.resource.ExternalCanister + ) { + const conflict = pairConflict(permission.resource.ExternalCanister.Call); + if (conflict) { + findings.push({ + checkId: 'external-call.validation-equals-execution', + severity: 'blocker', + location: `permission on CallExternalCanister`, + message: `Permission grants a CallExternalCanister resource with validation_method == execution_method (${conflict}). Validation hook runs before approval; if the method has side effects, the approval gate is bypassed.`, + fix: 'Remove or restructure the permission so the validation hook is a separate read-only method.', + }); + } + } + } + + return findings; +}; + +const pairConflict = (target: CallExternalCanisterResourceTarget): string | null => { + if (!('ValidationMethod' in target.validation_method)) return null; + if (!('ExecutionMethod' in target.execution_method)) return null; + const v = target.validation_method.ValidationMethod; + const e = target.execution_method.ExecutionMethod; + if (v.canister_id === e.canister_id && v.method_name === e.method_name) { + return `${v.canister_id}::${v.method_name}`; + } + return null; +}; diff --git a/cli/src/audit/checks/fixtures.ts b/cli/src/audit/checks/fixtures.ts new file mode 100644 index 000000000..534dba539 --- /dev/null +++ b/cli/src/audit/checks/fixtures.ts @@ -0,0 +1,67 @@ +import { + Asset, + CanisterMethod, + NamedRule, + Permission, + RequestPolicy, + RequestPolicyRule, + RequestSpecifier, + User, + UserGroup, +} from '../types'; + +let counter = 0; +const nextId = (): string => `id-${++counter}`; + +export const resetCounter = (): void => { + counter = 0; +}; + +export const makeUser = (overrides: Partial = {}): User => ({ + id: overrides.id ?? nextId(), + name: overrides.name ?? 'Test User', + status: overrides.status ?? { Active: null }, + groups: overrides.groups ?? [], + ...overrides, +}); + +export const makeGroup = (overrides: Partial = {}): UserGroup => ({ + id: overrides.id ?? nextId(), + name: overrides.name ?? 'Test Group', +}); + +export const makePolicy = ( + specifier: RequestSpecifier, + rule: RequestPolicyRule, +): RequestPolicy => ({ + id: nextId(), + specifier, + rule, +}); + +export const makeAsset = (overrides: Partial = {}): Asset => ({ + id: overrides.id ?? nextId(), + blockchain: overrides.blockchain ?? 'icp', + standards: overrides.standards ?? ['icp_native'], + symbol: overrides.symbol ?? 'ICP', + name: overrides.name ?? 'Test Asset', + metadata: overrides.metadata ?? [], + decimals: overrides.decimals ?? 8, +}); + +export const makeNamedRule = (overrides: Partial): NamedRule => ({ + id: overrides.id ?? nextId(), + name: overrides.name ?? 'Test Named Rule', + description: overrides.description ?? [], + rule: overrides.rule ?? { AutoApproved: null }, +}); + +export const makePermission = (resource: Permission['resource']): Permission => ({ + resource, + allow: {}, +}); + +export const method = (canister_id: string, method_name: string): CanisterMethod => ({ + canister_id, + method_name, +}); diff --git a/cli/src/audit/checks/quorum-empty-approver-set.spec.ts b/cli/src/audit/checks/quorum-empty-approver-set.spec.ts new file mode 100644 index 000000000..f5ec9c5f7 --- /dev/null +++ b/cli/src/audit/checks/quorum-empty-approver-set.spec.ts @@ -0,0 +1,118 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import { quorumEmptyApproverSet } from './quorum-empty-approver-set'; +import { makeGroup, makeNamedRule, makePolicy, makeUser, resetCounter } from './fixtures'; + +describe('quorum.empty-approver-set', () => { + beforeEach(() => resetCounter()); + + it('does not fire when the approver set has active users', () => { + const alice = makeUser({ id: 'u-alice', name: 'Alice' }); + const policy = makePolicy( + { Transfer: { Any: null } }, + { Quorum: { approvers: { Id: ['u-alice'] }, min_approved: 1 } }, + ); + const findings = quorumEmptyApproverSet([policy], [alice], []); + expect(findings).toHaveLength(0); + }); + + it('fires when a named user is inactive', () => { + const carol = makeUser({ + id: 'u-carol', + name: 'Carol', + status: { Inactive: null }, + }); + const policy = makePolicy( + { Transfer: { Any: null } }, + { Quorum: { approvers: { Id: ['u-carol'] }, min_approved: 1 } }, + ); + const findings = quorumEmptyApproverSet([policy], [carol], []); + expect(findings).toHaveLength(1); + expect(findings[0].severity).toBe('blocker'); + expect(findings[0].checkId).toBe('quorum.empty-approver-set'); + expect(findings[0].message).toMatch(/0 active users/); + }); + + it('fires when a group has zero members', () => { + const emptyGroup = makeGroup({ id: 'g-finance', name: 'Finance' }); + const someoneElse = makeUser({ id: 'u-other', name: 'Other' }); + // No users are in g-finance. + const policy = makePolicy( + { Transfer: { Any: null } }, + { Quorum: { approvers: { Group: ['g-finance'] }, min_approved: 2 } }, + ); + const findings = quorumEmptyApproverSet([policy], [someoneElse], []); + expect(findings).toHaveLength(1); + expect(findings[0].severity).toBe('blocker'); + void emptyGroup; + }); + + it('does not fire on UserSpecifier::Any when active users exist', () => { + const alice = makeUser({ id: 'u-alice' }); + const policy = makePolicy( + { Transfer: { Any: null } }, + { Quorum: { approvers: { Any: null }, min_approved: 1 } }, + ); + const findings = quorumEmptyApproverSet([policy], [alice], []); + expect(findings).toHaveLength(0); + }); + + it('does not fire when min_approved is 0 (rule asks for nothing)', () => { + const policy = makePolicy( + { Transfer: { Any: null } }, + { Quorum: { approvers: { Id: ['u-missing'] }, min_approved: 0 } }, + ); + const findings = quorumEmptyApproverSet([policy], [], []); + expect(findings).toHaveLength(0); + }); + + it('descends into AnyOf to flag a buried empty-approver rule', () => { + const carol = makeUser({ + id: 'u-carol', + status: { Inactive: null }, + }); + const policy = makePolicy( + { Transfer: { Any: null } }, + { + AnyOf: [ + { Quorum: { approvers: { Id: ['u-carol'] }, min_approved: 1 } }, + { AutoApproved: null }, + ], + }, + ); + const findings = quorumEmptyApproverSet([policy], [carol], []); + expect(findings).toHaveLength(1); + expect(findings[0].location).toMatch(/AnyOf\[0\]/); + }); + + it('resolves NamedRule references', () => { + const carol = makeUser({ + id: 'u-carol', + status: { Inactive: null }, + }); + const namedRule = makeNamedRule({ + id: 'nr-admin', + name: 'Admin approval', + rule: { Quorum: { approvers: { Id: ['u-carol'] }, min_approved: 1 } }, + }); + const policy = makePolicy({ Transfer: { Any: null } }, { NamedRule: 'nr-admin' }); + const findings = quorumEmptyApproverSet([policy], [carol], [namedRule]); + expect(findings).toHaveLength(1); + expect(findings[0].location).toMatch(/Admin approval/); + }); + + it('does not loop infinitely on cyclic NamedRule references', () => { + const ruleA = makeNamedRule({ + id: 'nr-a', + name: 'A', + rule: { NamedRule: 'nr-b' }, + }); + const ruleB = makeNamedRule({ + id: 'nr-b', + name: 'B', + rule: { NamedRule: 'nr-a' }, + }); + const policy = makePolicy({ AddUser: null }, { NamedRule: 'nr-a' }); + const findings = quorumEmptyApproverSet([policy], [], [ruleA, ruleB]); + expect(findings).toHaveLength(0); + }); +}); diff --git a/cli/src/audit/checks/quorum-empty-approver-set.ts b/cli/src/audit/checks/quorum-empty-approver-set.ts new file mode 100644 index 000000000..136d38ee0 --- /dev/null +++ b/cli/src/audit/checks/quorum-empty-approver-set.ts @@ -0,0 +1,39 @@ +import { Finding } from '../report'; +import { resolveApprovers, walkQuorumRules } from '../resolver'; +import { NamedRule, RequestPolicy, User, UUID } from '../types'; +import { describeSpecifier, describeRequestSpecifier } from './describe'; + +/** + * Flags Quorum / QuorumPercentage rules whose approver set resolves to zero + * active users today. + * + * Background: `RequestApprovalSummary::evaluate` clamps `min_approved` down to + * `total_possible_approvers`. When the approver set is empty the clamp drives + * the threshold to 0, and `approved (0) >= min_approved (0)` evaluates to + * Approved without any votes cast. The next matching request auto-approves. + */ +export const quorumEmptyApproverSet = ( + policies: RequestPolicy[], + users: User[], + namedRules: NamedRule[], +): Finding[] => { + const findings: Finding[] = []; + const namedByUUID = new Map(namedRules.map(r => [r.id, r])); + + for (const policy of policies) { + walkQuorumRules(policy.rule, namedByUUID, (kind, approvers, minApproved, path) => { + const resolved = resolveApprovers(approvers, users); + if (resolved.length === 0 && minApproved > 0) { + findings.push({ + checkId: 'quorum.empty-approver-set', + severity: 'blocker', + location: `policy ${policy.id} (${describeRequestSpecifier(policy.specifier)}) — ${path}`, + message: `${kind} rule asks for ${minApproved} approval(s) but ${describeSpecifier(approvers)} currently resolves to 0 active users. Next matching request will auto-approve.`, + fix: 'Add eligible approvers to this specifier, or wrap in AnyOf with an admin-group fallback before the next matching request is submitted.', + }); + } + }); + } + + return findings; +}; diff --git a/cli/src/audit/index.ts b/cli/src/audit/index.ts new file mode 100644 index 000000000..da5ba9efe --- /dev/null +++ b/cli/src/audit/index.ts @@ -0,0 +1,85 @@ +import { createCommand } from 'commander'; +import { writeFileSync } from 'fs'; +import { resolve } from 'path'; +import { assertReplicaIsHealthy } from '../utils'; +import { assetEditPolicyWeakerThanTransfer } from './checks/asset-edit-policy-weaker-than-transfer'; +import { externalCallValidationEqualsExecution } from './checks/external-call-validation-equals-execution'; +import { quorumEmptyApproverSet } from './checks/quorum-empty-approver-set'; +import { + listAssets, + listNamedRules, + listPermissions, + listRequestPolicies, + listUserGroups, + listUsers, + StationContext, +} from './station.core'; +import { AuditReport, exitCodeFor, Finding, renderReport } from './report'; + +const command = createCommand('audit').description( + 'Read-only sanity checks against an Orbit station configuration.', +); + +command + .requiredOption('-s, --station ', 'The station canister id to audit.') + .option('-n, --network ', 'The network the station lives on. Defaults to `ic`.', 'ic') + .option( + '-i, --identity ', + 'The dfx identity to call the station with (needs read access to list_* methods). Defaults to `default`.', + 'default', + ) + .option( + '-o, --output ', + 'Write the report to a file instead of stdout. The exit code is still set based on findings.', + ); + +command.action(async options => { + const ctx: StationContext = { + station: options.station, + network: options.network, + identity: options.identity, + }; + + await assertReplicaIsHealthy(ctx.network); + + const [policies, users, userGroups, assets, namedRules, permissions] = await Promise.all([ + listRequestPolicies(ctx), + listUsers(ctx), + listUserGroups(ctx), + listAssets(ctx), + listNamedRules(ctx), + listPermissions(ctx), + ]); + + const findings: Finding[] = [ + ...quorumEmptyApproverSet(policies, users, namedRules), + ...externalCallValidationEqualsExecution(policies, permissions), + ...assetEditPolicyWeakerThanTransfer(policies, users, namedRules), + ]; + + const confirmations: string[] = [ + `${policies.length} request policies loaded.`, + `${users.length} users loaded.`, + `${userGroups.length} user groups loaded.`, + `${assets.length} assets loaded.`, + `${namedRules.length} named rules loaded.`, + `${permissions.length} permissions loaded.`, + ]; + + const report: AuditReport = { ctx, findings, confirmations }; + const rendered = renderReport(report); + + if (options.output) { + const target = resolve(options.output); + writeFileSync(target, rendered + '\n', 'utf8'); + // Progress on stderr so file-output mode still gives feedback without + // polluting the report file or stdout pipes. + console.error(`Wrote audit report to ${target}`); + } else { + console.log(rendered); + } + + process.exit(exitCodeFor(report)); +}); + +export default command; diff --git a/cli/src/audit/report.ts b/cli/src/audit/report.ts new file mode 100644 index 000000000..ca1d72444 --- /dev/null +++ b/cli/src/audit/report.ts @@ -0,0 +1,85 @@ +import { StationContext } from './station.core'; + +export type Severity = 'blocker' | 'warning' | 'info'; + +export interface Finding { + checkId: string; + severity: Severity; + message: string; + location?: string; + fix?: string; +} + +export interface AuditReport { + ctx: StationContext; + findings: Finding[]; + confirmations: string[]; +} + +const RANK: Record = { blocker: 0, warning: 1, info: 2 }; + +const HEADER: Record = { + blocker: 'BLOCKERS', + warning: 'WARNINGS', + info: 'INFO', +}; + +const sortFindings = (findings: Finding[]): Finding[] => + [...findings].sort((a, b) => RANK[a.severity] - RANK[b.severity]); + +const groupBySeverity = (findings: Finding[]): Map => { + const map = new Map(); + for (const finding of findings) { + const bucket = map.get(finding.severity) ?? []; + bucket.push(finding); + map.set(finding.severity, bucket); + } + return map; +}; + +export const renderReport = (report: AuditReport): string => { + const lines: string[] = []; + lines.push('Orbit Station Audit Report'); + lines.push(`station: ${report.ctx.station}`); + lines.push(`network: ${report.ctx.network}`); + lines.push(''); + + const sorted = sortFindings(report.findings); + const grouped = groupBySeverity(sorted); + for (const severity of ['blocker', 'warning', 'info'] as Severity[]) { + const bucket = grouped.get(severity) ?? []; + if (bucket.length === 0) continue; + lines.push(`==== ${HEADER[severity]} (${bucket.length}) ====`); + lines.push(''); + for (const finding of bucket) { + lines.push(`[${finding.checkId}]`); + if (finding.location) lines.push(` ${finding.location}`); + lines.push(` ${finding.message}`); + if (finding.fix) lines.push(` fix: ${finding.fix}`); + lines.push(''); + } + } + + if (report.confirmations.length > 0) { + lines.push('==== Positive confirmations ===='); + for (const c of report.confirmations) lines.push(`- ${c}`); + lines.push(''); + } + + const counts = sorted.reduce( + (acc, f) => { + acc[f.severity]++; + return acc; + }, + { blocker: 0, warning: 0, info: 0 } as Record, + ); + lines.push(`summary: ${counts.blocker} blocker, ${counts.warning} warning, ${counts.info} info`); + + return lines.join('\n'); +}; + +export const exitCodeFor = (report: AuditReport): number => { + if (report.findings.some(f => f.severity === 'blocker')) return 2; + if (report.findings.some(f => f.severity === 'warning')) return 1; + return 0; +}; diff --git a/cli/src/audit/resolver.ts b/cli/src/audit/resolver.ts new file mode 100644 index 000000000..1bf2920eb --- /dev/null +++ b/cli/src/audit/resolver.ts @@ -0,0 +1,142 @@ +import { NamedRule, RequestPolicyRule, User, UserSpecifier, UUID } from './types'; + +/** + * The set of users that *could* approve a rule, mirroring the station's + * `find_matching_users` semantics in `core/station/impl/src/models/request_policy_rule.rs`. + * Only the request-agnostic `UserSpecifier` variants (`Any`, `Id`, `Group`) are + * resolvable statically without a concrete request to evaluate against. + */ +export const resolveApprovers = (specifier: UserSpecifier, users: User[]): User[] => { + const active = users.filter(u => 'Active' in u.status); + if ('Any' in specifier) return active; + if ('Id' in specifier) { + const wanted = new Set(specifier.Id); + return active.filter(u => wanted.has(u.id)); + } + if ('Group' in specifier) { + const wanted = new Set(specifier.Group); + return active.filter(u => u.groups.some(g => wanted.has(g.id))); + } + return []; +}; + +/** + * Walks the rule graph, calling `visit` on every Quorum / QuorumPercentage + * encountered. Resolves `NamedRule` references on the way; cycles are detected + * via a visited set so a self-referential rule cannot loop the walker. + * + * `path` accumulates the human-readable breadcrumb to the rule for reporting. + */ +export const walkQuorumRules = ( + rule: RequestPolicyRule, + namedRules: Map, + visit: ( + kind: 'Quorum' | 'QuorumPercentage', + approvers: UserSpecifier, + minApproved: number, + path: string, + ) => void, + path: string = '', + visited: Set = new Set(), +): void => { + if ('AutoApproved' in rule || 'AllowListed' in rule || 'AllowListedByMetadata' in rule) { + return; + } + if ('Quorum' in rule) { + visit('Quorum', rule.Quorum.approvers, rule.Quorum.min_approved, path || 'Quorum'); + return; + } + if ('QuorumPercentage' in rule) { + visit( + 'QuorumPercentage', + rule.QuorumPercentage.approvers, + rule.QuorumPercentage.min_approved, + path || 'QuorumPercentage', + ); + return; + } + if ('AnyOf' in rule) { + rule.AnyOf.forEach((child, idx) => + walkQuorumRules(child, namedRules, visit, joinPath(path, `AnyOf[${idx}]`), visited), + ); + return; + } + if ('AllOf' in rule) { + rule.AllOf.forEach((child, idx) => + walkQuorumRules(child, namedRules, visit, joinPath(path, `AllOf[${idx}]`), visited), + ); + return; + } + if ('Not' in rule) { + walkQuorumRules(rule.Not, namedRules, visit, joinPath(path, 'Not'), visited); + return; + } + if ('NamedRule' in rule) { + const id = rule.NamedRule; + if (visited.has(id)) return; + const named = namedRules.get(id); + if (!named) return; + walkQuorumRules( + named.rule, + namedRules, + visit, + joinPath(path, `NamedRule("${named.name}")`), + new Set([...visited, id]), + ); + } +}; + +const joinPath = (parent: string, segment: string): string => + parent ? `${parent} → ${segment}` : segment; + +/** + * Returns the minimum number of approval votes required to satisfy a rule, given + * the current set of active users. Mirrors the station evaluator's clamp: + * `min(min_approved, total_possible_approvers)`. + * + * Combinators reduce as: + * AnyOf → min over children (easiest path) + * AllOf → max over children (lower-bound; semantically each child needs its own quorum) + * Not → opaque, treated as Infinity (cannot reason statically about negation) + * + * `AutoApproved` and `AllowListed*` return 0 — rules that bypass vote counting. + * Unresolvable cases (cycle, missing NamedRule) return Infinity to avoid false positives. + */ +export const minVotesForRule = ( + rule: RequestPolicyRule, + users: User[], + namedRules: Map, + visited: Set = new Set(), +): number => { + if ('AutoApproved' in rule) return 0; + if ('AllowListed' in rule || 'AllowListedByMetadata' in rule) return 0; + if ('Quorum' in rule) { + const resolved = resolveApprovers(rule.Quorum.approvers, users); + return Math.min(rule.Quorum.min_approved, resolved.length); + } + if ('QuorumPercentage' in rule) { + const resolved = resolveApprovers(rule.QuorumPercentage.approvers, users); + const scaled = rule.QuorumPercentage.min_approved * resolved.length; + return scaled === 0 ? 0 : Math.ceil(scaled / 100); + } + if ('AnyOf' in rule) { + return rule.AnyOf.reduce( + (acc, child) => Math.min(acc, minVotesForRule(child, users, namedRules, visited)), + Number.POSITIVE_INFINITY, + ); + } + if ('AllOf' in rule) { + return rule.AllOf.reduce( + (acc, child) => Math.max(acc, minVotesForRule(child, users, namedRules, visited)), + 0, + ); + } + if ('Not' in rule) return Number.POSITIVE_INFINITY; + if ('NamedRule' in rule) { + if (visited.has(rule.NamedRule)) return Number.POSITIVE_INFINITY; + const named = namedRules.get(rule.NamedRule); + if (!named) return Number.POSITIVE_INFINITY; + return minVotesForRule(named.rule, users, namedRules, new Set([...visited, rule.NamedRule])); + } + return Number.POSITIVE_INFINITY; +}; diff --git a/cli/src/audit/station.core.ts b/cli/src/audit/station.core.ts new file mode 100644 index 000000000..bb80887b5 --- /dev/null +++ b/cli/src/audit/station.core.ts @@ -0,0 +1,121 @@ +import { execAsync } from '../utils'; +import { Asset, NamedRule, Permission, RequestPolicy, User, UserGroup } from './types'; + +export interface StationContext { + station: string; + network: string; + identity: string; +} + +const PAGE_SIZE = 50; + +const dfx = async (ctx: StationContext, method: string, args: string): Promise => { + // Candid arguments are always tuples — even single-arg calls need the outer `( ... )`. + const cmd = `dfx canister call --identity '${ctx.identity}' --network '${ctx.network}' --output json '${ctx.station}' ${method} '(${args})'`; + const raw = await execAsync(cmd); + return JSON.parse(raw); +}; + +const unwrapOk = (response: unknown, method: string): T => { + if (response && typeof response === 'object' && 'Ok' in response) { + return (response as { Ok: T }).Ok; + } + throw new Error(`Station call '${method}' failed: ${JSON.stringify(response)}`); +}; + +const readOffset = (next_offset: unknown): number => { + if (Array.isArray(next_offset) && next_offset.length > 0) { + return Number(next_offset[0]); + } + return 0; +}; + +export const listRequestPolicies = async (ctx: StationContext): Promise => { + const policies: RequestPolicy[] = []; + let offset = 0; + do { + const args = `record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }`; + const response = await dfx(ctx, 'list_request_policies', args); + const page = unwrapOk<{ policies: RequestPolicy[]; next_offset?: unknown }>( + response, + 'list_request_policies', + ); + policies.push(...page.policies); + offset = readOffset(page.next_offset); + } while (offset > 0); + return policies; +}; + +export const listUsers = async (ctx: StationContext): Promise => { + const users: User[] = []; + let offset = 0; + do { + const args = `record { paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; statuses = null; groups = null; search_term = null; }`; + const response = await dfx(ctx, 'list_users', args); + const page = unwrapOk<{ users: User[]; next_offset?: unknown }>(response, 'list_users'); + users.push(...page.users); + offset = readOffset(page.next_offset); + } while (offset > 0); + return users; +}; + +export const listUserGroups = async (ctx: StationContext): Promise => { + const groups: UserGroup[] = []; + let offset = 0; + do { + const args = `record { paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; search_term = null; }`; + const response = await dfx(ctx, 'list_user_groups', args); + const page = unwrapOk<{ user_groups: UserGroup[]; next_offset?: unknown }>( + response, + 'list_user_groups', + ); + groups.push(...page.user_groups); + offset = readOffset(page.next_offset); + } while (offset > 0); + return groups; +}; + +export const listAssets = async (ctx: StationContext): Promise => { + const assets: Asset[] = []; + let offset = 0; + do { + const args = `record { paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; }`; + const response = await dfx(ctx, 'list_assets', args); + const page = unwrapOk<{ assets: Asset[]; next_offset?: unknown }>(response, 'list_assets'); + assets.push(...page.assets); + offset = readOffset(page.next_offset); + } while (offset > 0); + return assets; +}; + +export const listPermissions = async (ctx: StationContext): Promise => { + const permissions: Permission[] = []; + let offset = 0; + do { + const args = `record { resources = null; paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; }`; + const response = await dfx(ctx, 'list_permissions', args); + const page = unwrapOk<{ permissions: Permission[]; next_offset?: unknown }>( + response, + 'list_permissions', + ); + permissions.push(...page.permissions); + offset = readOffset(page.next_offset); + } while (offset > 0); + return permissions; +}; + +export const listNamedRules = async (ctx: StationContext): Promise => { + const rules: NamedRule[] = []; + let offset = 0; + do { + const args = `record { paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; }`; + const response = await dfx(ctx, 'list_named_rules', args); + const page = unwrapOk<{ named_rules: NamedRule[]; next_offset?: unknown }>( + response, + 'list_named_rules', + ); + rules.push(...page.named_rules); + offset = readOffset(page.next_offset); + } while (offset > 0); + return rules; +}; diff --git a/cli/src/audit/types.ts b/cli/src/audit/types.ts new file mode 100644 index 000000000..7da477bb5 --- /dev/null +++ b/cli/src/audit/types.ts @@ -0,0 +1,146 @@ +// Hand-written TypeScript subset of the station API. Only the types the audit +// command needs are modelled here; everything else is opaque. Keeping the +// surface small avoids a `didc bind` dependency for the first audit release. +// +// Cross-reference: core/station/api/spec.did. + +export type UUID = string; + +export type UserStatus = { Active: null } | { Inactive: null }; + +export interface UserGroup { + id: UUID; + name: string; +} + +export interface User { + id: UUID; + name: string; + status: UserStatus; + groups: UserGroup[]; +} + +export type UserSpecifier = { Any: null } | { Id: UUID[] } | { Group: UUID[] }; + +export interface Quorum { + approvers: UserSpecifier; + min_approved: number; +} + +export interface QuorumPercentage { + approvers: UserSpecifier; + min_approved: number; +} + +export type RequestPolicyRule = + | { AutoApproved: null } + | { Quorum: Quorum } + | { QuorumPercentage: QuorumPercentage } + | { AllowListed: null } + | { AllowListedByMetadata: unknown } + | { AnyOf: RequestPolicyRule[] } + | { AllOf: RequestPolicyRule[] } + | { Not: RequestPolicyRule } + | { NamedRule: UUID }; + +export type ResourceId = { Any: null } | { Id: UUID }; +export type ResourceIds = { Any: null } | { Ids: UUID[] }; + +export interface CanisterMethod { + canister_id: string; + method_name: string; +} + +export type ValidationMethodResourceTarget = { No: null } | { ValidationMethod: CanisterMethod }; + +export type ExecutionMethodResourceTarget = { Any: null } | { ExecutionMethod: CanisterMethod }; + +export interface CallExternalCanisterResourceTarget { + validation_method: ValidationMethodResourceTarget; + execution_method: ExecutionMethodResourceTarget; +} + +export type RequestSpecifier = + | { AddAccount: null } + | { AddUser: null } + | { EditAccount: ResourceIds } + | { EditUser: ResourceIds } + | { Transfer: ResourceIds } + | { AddAddressBookEntry: null } + | { EditAddressBookEntry: ResourceIds } + | { RemoveAddressBookEntry: ResourceIds } + | { SystemUpgrade: null } + | { SetDisasterRecovery: null } + | { ChangeExternalCanister: unknown } + | { FundExternalCanister: unknown } + | { CreateExternalCanister: null } + | { CallExternalCanister: CallExternalCanisterResourceTarget } + | { EditPermission: unknown } + | { AddRequestPolicy: null } + | { EditRequestPolicy: ResourceIds } + | { RemoveRequestPolicy: ResourceIds } + | { AddUserGroup: null } + | { EditUserGroup: ResourceIds } + | { RemoveUserGroup: ResourceIds } + | { ManageSystemInfo: null } + | { AddAsset: null } + | { EditAsset: ResourceIds } + | { RemoveAsset: ResourceIds } + | { AddNamedRule: null } + | { EditNamedRule: ResourceIds } + | { RemoveNamedRule: ResourceIds }; + +export interface RequestPolicy { + id: UUID; + specifier: RequestSpecifier; + rule: RequestPolicyRule; +} + +export interface AssetMetadata { + key: string; + value: string; +} + +export interface Asset { + id: UUID; + blockchain: string; + standards: string[]; + symbol: string; + name: string; + metadata: AssetMetadata[]; + decimals: number; +} + +export interface NamedRule { + id: UUID; + name: string; + description?: [string] | []; + rule: RequestPolicyRule; +} + +export type ExternalCanisterResourceAction = + | { List: null } + | { Create: null } + | { Change: unknown } + | { Read: unknown } + | { Fund: unknown } + | { Call: CallExternalCanisterResourceTarget }; + +export type Resource = + | { Permission: unknown } + | { Account: unknown } + | { AddressBook: unknown } + | { ExternalCanister: ExternalCanisterResourceAction } + | { Notification: unknown } + | { Request: unknown } + | { RequestPolicy: unknown } + | { System: unknown } + | { User: unknown } + | { UserGroup: unknown } + | { Asset: unknown } + | { NamedRule: unknown }; + +export interface Permission { + resource: Resource; + allow: unknown; +} diff --git a/cli/src/cli.ts b/cli/src/cli.ts index c148784f5..771b4d638 100755 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -1,5 +1,6 @@ import { program } from 'commander'; import { version } from '../package.json'; +import audit from './audit'; import release from './release'; import registry from './registry'; @@ -12,6 +13,7 @@ program .description('Print the path to the Orbit CLI') .action(() => console.log(__dirname)); +program.addCommand(audit); program.addCommand(release); program.addCommand(registry); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 241d136c8..61a286ab1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,6 +225,9 @@ importers: commander: specifier: 12.1.0 version: 12.1.0 + vitest: + specifier: 1.6.1 + version: 1.6.1(@types/node@22.7.4)(jsdom@24.1.3)(lightningcss@1.29.1)(sass@1.77.1) docs: dependencies: From 3ee16cc88f1fa66cfaaab56e9607ececa3119e76 Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 16:33:40 +0200 Subject: [PATCH 02/13] docs(cli): trim audit README to usage + report + extending --- cli/src/audit/README.md | 48 ----------------------------------------- 1 file changed, 48 deletions(-) diff --git a/cli/src/audit/README.md b/cli/src/audit/README.md index 3216d8532..c25134303 100644 --- a/cli/src/audit/README.md +++ b/cli/src/audit/README.md @@ -43,34 +43,6 @@ orbit-cli audit --station rrkah-fqaaa-aaaaa-aaaaq-cai \ orbit-cli audit --station --network local ``` -## What it checks - -The first release ships three checks. Each finding has a stable check id (so configs and CI rules can refer to them without breaking across versions) and a severity (`BLOCKER`, `WARNING`, or `INFO`). - -### `quorum.empty-approver-set` — `BLOCKER` - -Flags `Quorum` / `QuorumPercentage` rules whose `UserSpecifier` currently resolves to **zero active users**. - -`RequestApprovalSummary::evaluate` clamps `min_approved` down to the number of possible approvers. When the approver set is empty the clamp drives the threshold to `0`, and `approved (0) >= min_approved (0)` evaluates the rule to `Approved` without any votes cast. The next matching request auto-approves silently. - -The check walks every `RequestPolicy.rule`, including nested combinators (`AnyOf`, `AllOf`, `Not`) and `NamedRule` references. Cycles in `NamedRule` resolution are detected and skipped. - -### `external-call.validation-equals-execution` — `BLOCKER` - -Flags `CallExternalCanister` permissions and request policies whose `validation_method` and `execution_method` resolve to the same `(canister_id, method_name)` pair. - -When a `CallExternalCanister` request is submitted, the station invokes the configured `validation_method` immediately to render and validate the argument blob — before the approval policy has completed. If that method is also the `execution_method`, its side effect runs at submission time, bypassing the approval gate. There is no benign reason for the pair to match. - -The check sweeps both surfaces — request policies (`RequestSpecifier::CallExternalCanister`) and permissions (`Resource::ExternalCanister(Call(...))`). - -### `asset.edit-policy-weaker-than-transfer` — `WARNING` - -Warns when the **easiest** path to passing an `EditAsset` request requires fewer approvals than the **strictest** `Transfer` policy in the station. - -An approved transfer's destination ledger is resolved from `asset.metadata["ledger_canister_id"]` at execute time. A successful `EditAsset` between approval and execution can therefore redirect routing. When `EditAsset` is gated more loosely than `Transfer`, an actor who can pass the lower bar can subvert the higher one. - -The MVP compares the easiest `EditAsset` path against the strictest `Transfer` path station-wide. Per-asset / per-account scoping is a planned follow-up. - ## Report format The report is plain text, sorted by severity (`BLOCKER` → `WARNING` → `INFO`), with each finding showing the offending policy / asset / permission and a suggested fix. A `summary:` line ends the report with counts. @@ -97,13 +69,6 @@ network: ic summary: 1 blocker, 0 warning, 0 info ``` -## Implementation notes - -- **No new runtime dependencies.** The audit uses the same `dfx canister call --output json` pattern as the existing `registry/` and `release/` subcommands. The result is parsed as Candid-in-JSON. -- **Hand-written types.** The audit imports a small subset of the station API as hand-written TypeScript in [types.ts](./types.ts) instead of generating bindings with `didc bind`. The surface is small enough that the extra build step isn't worth it for this MVP. If the audit grows, switching to generated bindings is a straightforward refactor. -- **Specifier resolution mirrors the station evaluator.** `resolveApprovers` and `minVotesForRule` in [resolver.ts](./resolver.ts) reimplement the relevant subset of `find_matching_users` and `RequestApprovalSummary::evaluate` from `core/station/impl/src/models/request_policy_rule.rs`. If the station evaluator changes, the resolver may need updating; the tests cover the static rule shapes used today. -- **Read-only by construction.** Only query methods are called. The audit cannot mutate state under any circumstance. - ## Tests ```bash @@ -112,8 +77,6 @@ pnpm --filter orbit-cli test Unit tests cover each check across positive and negative cases, including combinator descent and cycle detection on `NamedRule` references. Fixtures are in [checks/fixtures.ts](./checks/fixtures.ts). -The audit's check logic is exercised by these tests; the dfx-call plumbing is exercised end-to-end against a real station (see the example invocations above). - ## Extending Each check is a function that takes the loaded station data and returns `Finding[]`: @@ -133,14 +96,3 @@ To add a new check: 1. Drop a new file in `checks/` returning `Finding[]`. Use a stable check id under a dot-namespace (`category.short-name`). 2. Spec it in `checks/.spec.ts` with at least one positive and one negative case. Reuse the fixture builders in `checks/fixtures.ts`. 3. Wire it into `index.ts` alongside the existing checks. - -A planned follow-up will move this to a registry pattern with a stable plugin interface so third-party checks can be loaded without modifying core. - -## Roadmap - -Possible follow-ups (not committed): - -- `--format json` for CI / pipeline consumption. -- Config file with per-check enable/disable + severity overrides + threshold knobs. -- Plugin loader so out-of-tree checks can be added without forking. -- Additional checks: small-group Quorum specifiers (WARNING), missing admin-group fallback (INFO), dangling user/group references (WARNING), per-asset `EditAsset` vs `Transfer` scoping (refines the current station-wide check). From 825b5ef0d63bf7bb6b29f410608257b0b48fc907 Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 16:46:11 +0200 Subject: [PATCH 03/13] docs(cli): note that audit reports contain station identifiers --- cli/src/audit/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/src/audit/README.md b/cli/src/audit/README.md index c25134303..a84510ea6 100644 --- a/cli/src/audit/README.md +++ b/cli/src/audit/README.md @@ -69,6 +69,8 @@ network: ic summary: 1 blocker, 0 warning, 0 info ``` +Findings include policy, user, group, canister, and method identifiers. When using `--output`, write to a location that's appropriate for that level of detail — the report is internal station metadata, not something you'd want to commit to a public repo or sync to a shared drive without thinking about it first. + ## Tests ```bash From 3eab3299e0f68004ac92e715a083c6f0063a1af2 Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 16:49:43 +0200 Subject: [PATCH 04/13] fix(cli): address copilot review on audit subcommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - station.core: shell-escape `--station`, `--network`, `--identity` so a value containing `'` cannot break out of the surrounding quoting and inject shell syntax. - quorum.empty-approver-set: when the rule is `QuorumPercentage`, the `min_approved` field is a percentage rather than a vote count — render the finding message accordingly so operators read it correctly. Added a positive test for the percentage wording. - asset.edit-policy-weaker-than-transfer: clarify in the finding message that the reported approval counts are lower-bound estimates (the `minVotesForRule` helper reduces `AllOf` via max-of-children, so the true minimum may be higher). Tests updated to match. --- .../asset-edit-policy-weaker-than-transfer.spec.ts | 6 +++--- .../checks/asset-edit-policy-weaker-than-transfer.ts | 2 +- .../audit/checks/quorum-empty-approver-set.spec.ts | 12 ++++++++++++ cli/src/audit/checks/quorum-empty-approver-set.ts | 10 +++++++++- cli/src/audit/station.core.ts | 9 ++++++++- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.spec.ts b/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.spec.ts index 9ab4434e7..c4670ee08 100644 --- a/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.spec.ts +++ b/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.spec.ts @@ -54,8 +54,8 @@ describe('asset.edit-policy-weaker-than-transfer', () => { const findings = assetEditPolicyWeakerThanTransfer([editPolicy, transferPolicy], users, []); expect(findings).toHaveLength(1); expect(findings[0].severity).toBe('warning'); - expect(findings[0].message).toMatch(/Easiest EditAsset path requires 1/); - expect(findings[0].message).toMatch(/strictest Transfer path requires 3/); + expect(findings[0].message).toMatch(/Easiest EditAsset path requires at least 1/); + expect(findings[0].message).toMatch(/strictest Transfer path requires at least 3/); }); it('uses minVotes including AutoApproved (treated as 0)', () => { @@ -67,6 +67,6 @@ describe('asset.edit-policy-weaker-than-transfer', () => { ); const findings = assetEditPolicyWeakerThanTransfer([editPolicy, transferPolicy], users, []); expect(findings).toHaveLength(1); - expect(findings[0].message).toMatch(/Easiest EditAsset path requires 0/); + expect(findings[0].message).toMatch(/Easiest EditAsset path requires at least 0/); }); }); diff --git a/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.ts b/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.ts index c7168912d..041d60857 100644 --- a/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.ts +++ b/cli/src/audit/checks/asset-edit-policy-weaker-than-transfer.ts @@ -42,7 +42,7 @@ export const assetEditPolicyWeakerThanTransfer = ( checkId: 'asset.edit-policy-weaker-than-transfer', severity: 'warning', location: `station-wide (${editPolicies.length} EditAsset policy/policies vs ${transferPolicies.length} Transfer policy/policies)`, - message: `Easiest EditAsset path requires ${easiestEdit} approval(s); strictest Transfer path requires ${strictestTransfer}. An attacker who can pass the EditAsset bar can mutate asset.metadata["ledger_canister_id"] and redirect an approved Transfer between approval and execution.`, + message: `Easiest EditAsset path requires at least ${easiestEdit} approval(s); strictest Transfer path requires at least ${strictestTransfer} (estimates — AllOf combinators are reduced via max-of-children, giving a lower bound). An actor who can pass the EditAsset bar can mutate asset.metadata["ledger_canister_id"] and redirect an approved Transfer between approval and execution.`, fix: 'Gate EditAsset at the same approval level as Transfer for the affected assets. Until v0.2 supports per-asset scoping, treat this as a station-wide signal to tighten the loosest EditAsset policy.', }, ]; diff --git a/cli/src/audit/checks/quorum-empty-approver-set.spec.ts b/cli/src/audit/checks/quorum-empty-approver-set.spec.ts index f5ec9c5f7..2dab45f18 100644 --- a/cli/src/audit/checks/quorum-empty-approver-set.spec.ts +++ b/cli/src/audit/checks/quorum-empty-approver-set.spec.ts @@ -100,6 +100,18 @@ describe('quorum.empty-approver-set', () => { expect(findings[0].location).toMatch(/Admin approval/); }); + it('renders QuorumPercentage findings as a percentage, not a vote count', () => { + const carol = makeUser({ id: 'u-carol', status: { Inactive: null } }); + const policy = makePolicy( + { Transfer: { Any: null } }, + { QuorumPercentage: { approvers: { Id: ['u-carol'] }, min_approved: 50 } }, + ); + const findings = quorumEmptyApproverSet([policy], [carol], []); + expect(findings).toHaveLength(1); + expect(findings[0].message).toMatch(/50% of approvers/); + expect(findings[0].message).not.toMatch(/50 approval/); + }); + it('does not loop infinitely on cyclic NamedRule references', () => { const ruleA = makeNamedRule({ id: 'nr-a', diff --git a/cli/src/audit/checks/quorum-empty-approver-set.ts b/cli/src/audit/checks/quorum-empty-approver-set.ts index 136d38ee0..3f0e36837 100644 --- a/cli/src/audit/checks/quorum-empty-approver-set.ts +++ b/cli/src/audit/checks/quorum-empty-approver-set.ts @@ -24,11 +24,19 @@ export const quorumEmptyApproverSet = ( walkQuorumRules(policy.rule, namedByUUID, (kind, approvers, minApproved, path) => { const resolved = resolveApprovers(approvers, users); if (resolved.length === 0 && minApproved > 0) { + // `min_approved` is a vote count for Quorum and a percentage (0-100) + // for QuorumPercentage. The empty-set bug fires identically in both + // cases (`percentage * 0 / 100 == 0`), but the report wording differs + // so operators read it correctly. + const requirement = + kind === 'QuorumPercentage' + ? `${minApproved}% of approvers` + : `${minApproved} approval(s)`; findings.push({ checkId: 'quorum.empty-approver-set', severity: 'blocker', location: `policy ${policy.id} (${describeRequestSpecifier(policy.specifier)}) — ${path}`, - message: `${kind} rule asks for ${minApproved} approval(s) but ${describeSpecifier(approvers)} currently resolves to 0 active users. Next matching request will auto-approve.`, + message: `${kind} rule asks for ${requirement} but ${describeSpecifier(approvers)} currently resolves to 0 active users. Next matching request will auto-approve.`, fix: 'Add eligible approvers to this specifier, or wrap in AnyOf with an admin-group fallback before the next matching request is submitted.', }); } diff --git a/cli/src/audit/station.core.ts b/cli/src/audit/station.core.ts index bb80887b5..a0559baef 100644 --- a/cli/src/audit/station.core.ts +++ b/cli/src/audit/station.core.ts @@ -9,9 +9,16 @@ export interface StationContext { const PAGE_SIZE = 50; +// Escapes a value for safe inclusion in a single-quoted POSIX shell argument. +// A literal `'` inside single quotes is impossible, so the standard idiom is to +// close the string, emit an escaped quote, and reopen: foo'bar -> 'foo'\''bar'. +const shq = (value: string): string => `'${value.replace(/'/g, "'\\''")}'`; + const dfx = async (ctx: StationContext, method: string, args: string): Promise => { // Candid arguments are always tuples — even single-arg calls need the outer `( ... )`. - const cmd = `dfx canister call --identity '${ctx.identity}' --network '${ctx.network}' --output json '${ctx.station}' ${method} '(${args})'`; + // `method` and `args` come from this file (not user input); `identity` / `network` / + // `station` come from CLI flags and are shell-escaped to prevent injection. + const cmd = `dfx canister call --identity ${shq(ctx.identity)} --network ${shq(ctx.network)} --output json ${shq(ctx.station)} ${method} '(${args})'`; const raw = await execAsync(cmd); return JSON.parse(raw); }; From 66ee6846419a3800b8c047ac212c7a69f8e9e6e1 Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 17:59:01 +0200 Subject: [PATCH 05/13] test(cli): direct unit tests for audit resolver --- cli/src/audit/resolver.spec.ts | 271 +++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 cli/src/audit/resolver.spec.ts diff --git a/cli/src/audit/resolver.spec.ts b/cli/src/audit/resolver.spec.ts new file mode 100644 index 000000000..b0690359d --- /dev/null +++ b/cli/src/audit/resolver.spec.ts @@ -0,0 +1,271 @@ +import { describe, expect, it, beforeEach } from 'vitest'; +import { minVotesForRule, resolveApprovers, walkQuorumRules } from './resolver'; +import { makeGroup, makeNamedRule, makeUser, resetCounter } from './checks/fixtures'; +import { NamedRule, RequestPolicyRule, User, UUID, UserSpecifier } from './types'; + +const namedMap = (rules: NamedRule[]): Map => new Map(rules.map(r => [r.id, r])); + +describe('resolveApprovers', () => { + beforeEach(() => resetCounter()); + + it('Any returns every active user, regardless of group', () => { + const alice = makeUser({ id: 'u-alice' }); + const bob = makeUser({ id: 'u-bob' }); + const carol = makeUser({ id: 'u-carol', status: { Inactive: null } }); + expect(resolveApprovers({ Any: null }, [alice, bob, carol]).map(u => u.id)).toEqual([ + 'u-alice', + 'u-bob', + ]); + }); + + it('Id returns only the named users (active only)', () => { + const alice = makeUser({ id: 'u-alice' }); + const bob = makeUser({ id: 'u-bob' }); + const carol = makeUser({ id: 'u-carol', status: { Inactive: null } }); + expect( + resolveApprovers({ Id: ['u-alice', 'u-carol'] }, [alice, bob, carol]).map(u => u.id), + ).toEqual(['u-alice']); + }); + + it('Id with no matching users returns empty', () => { + const alice = makeUser({ id: 'u-alice' }); + expect(resolveApprovers({ Id: ['u-missing'] }, [alice])).toEqual([]); + }); + + it('Group matches active users in any of the named groups', () => { + const finance = makeGroup({ id: 'g-finance' }); + const ops = makeGroup({ id: 'g-ops' }); + const alice = makeUser({ id: 'u-alice', groups: [finance] }); + const bob = makeUser({ id: 'u-bob', groups: [ops] }); + const carol = makeUser({ id: 'u-carol', groups: [], status: { Inactive: null } }); + expect( + resolveApprovers({ Group: ['g-finance', 'g-ops'] }, [alice, bob, carol]).map(u => u.id), + ).toEqual(['u-alice', 'u-bob']); + }); + + it('Group excludes users not in any of the named groups', () => { + const finance = makeGroup({ id: 'g-finance' }); + const alice = makeUser({ id: 'u-alice', groups: [finance] }); + const bob = makeUser({ id: 'u-bob', groups: [] }); + expect(resolveApprovers({ Group: ['g-finance'] }, [alice, bob]).map(u => u.id)).toEqual([ + 'u-alice', + ]); + }); + + it('Group excludes inactive users even if they are in the group', () => { + const finance = makeGroup({ id: 'g-finance' }); + const carol = makeUser({ + id: 'u-carol', + groups: [finance], + status: { Inactive: null }, + }); + expect(resolveApprovers({ Group: ['g-finance'] }, [carol])).toEqual([]); + }); +}); + +describe('walkQuorumRules', () => { + beforeEach(() => resetCounter()); + + const collect = (rule: RequestPolicyRule, named: NamedRule[] = []) => { + const seen: Array<{ kind: string; minApproved: number; path: string }> = []; + walkQuorumRules(rule, namedMap(named), (kind, _approvers, minApproved, path) => { + seen.push({ kind, minApproved, path }); + }); + return seen; + }; + + it('visits Quorum with the configured min_approved', () => { + expect(collect({ Quorum: { approvers: { Any: null }, min_approved: 2 } })).toEqual([ + { kind: 'Quorum', minApproved: 2, path: 'Quorum' }, + ]); + }); + + it('visits QuorumPercentage with the configured percentage', () => { + expect(collect({ QuorumPercentage: { approvers: { Any: null }, min_approved: 51 } })).toEqual([ + { kind: 'QuorumPercentage', minApproved: 51, path: 'QuorumPercentage' }, + ]); + }); + + it('skips AutoApproved and AllowListed', () => { + expect(collect({ AutoApproved: null })).toEqual([]); + expect(collect({ AllowListed: null })).toEqual([]); + }); + + // The path reports the breadcrumb up to but not including the rule itself; + // the rule kind is delivered separately via the visitor signature. + it('descends into AnyOf with indexed path breadcrumbs', () => { + const seen = collect({ + AnyOf: [ + { Quorum: { approvers: { Any: null }, min_approved: 1 } }, + { Quorum: { approvers: { Any: null }, min_approved: 2 } }, + ], + }); + expect(seen.map(s => s.path)).toEqual(['AnyOf[0]', 'AnyOf[1]']); + }); + + it('descends into AllOf with indexed path breadcrumbs', () => { + const seen = collect({ + AllOf: [{ Quorum: { approvers: { Any: null }, min_approved: 3 } }], + }); + expect(seen[0].path).toBe('AllOf[0]'); + }); + + it('descends into Not', () => { + const seen = collect({ + Not: { Quorum: { approvers: { Any: null }, min_approved: 1 } }, + }); + expect(seen[0].path).toBe('Not'); + }); + + it('resolves NamedRule and includes its name in the path', () => { + const admin = makeNamedRule({ + id: 'nr-admin', + name: 'Admin approval', + rule: { Quorum: { approvers: { Any: null }, min_approved: 2 } }, + }); + const seen = collect({ NamedRule: 'nr-admin' }, [admin]); + expect(seen[0].path).toBe('NamedRule("Admin approval")'); + }); + + it('joins nested combinators with arrows', () => { + const seen = collect({ + AnyOf: [ + { + AllOf: [{ Quorum: { approvers: { Any: null }, min_approved: 1 } }], + }, + ], + }); + expect(seen[0].path).toBe('AnyOf[0] → AllOf[0]'); + }); + + it('silently skips a missing NamedRule', () => { + expect(collect({ NamedRule: 'nr-does-not-exist' }, [])).toEqual([]); + }); + + it('breaks cycles in NamedRule references', () => { + const a = makeNamedRule({ id: 'nr-a', name: 'A', rule: { NamedRule: 'nr-b' } }); + const b = makeNamedRule({ id: 'nr-b', name: 'B', rule: { NamedRule: 'nr-a' } }); + expect(collect({ NamedRule: 'nr-a' }, [a, b])).toEqual([]); + }); +}); + +describe('minVotesForRule', () => { + beforeEach(() => resetCounter()); + + const noUsers: User[] = []; + const noNamed = new Map(); + const fiveActive = () => Array.from({ length: 5 }, (_, i) => makeUser({ id: `u-${i}` })); + + it('AutoApproved returns 0', () => { + expect(minVotesForRule({ AutoApproved: null }, noUsers, noNamed)).toBe(0); + }); + + it('AllowListed and AllowListedByMetadata return 0', () => { + expect(minVotesForRule({ AllowListed: null }, noUsers, noNamed)).toBe(0); + expect( + minVotesForRule({ AllowListedByMetadata: { key: 'k', value: 'v' } }, noUsers, noNamed), + ).toBe(0); + }); + + it('Quorum returns min_approved when enough active approvers exist', () => { + const users = fiveActive(); + const rule: RequestPolicyRule = { Quorum: { approvers: { Any: null }, min_approved: 3 } }; + expect(minVotesForRule(rule, users, noNamed)).toBe(3); + }); + + it('Quorum clamps to the number of active approvers', () => { + const users = [makeUser({ id: 'u-1' })]; + const rule: RequestPolicyRule = { Quorum: { approvers: { Any: null }, min_approved: 5 } }; + expect(minVotesForRule(rule, users, noNamed)).toBe(1); + }); + + it('Quorum returns 0 when the approver set is empty', () => { + const rule: RequestPolicyRule = { + Quorum: { approvers: { Id: ['u-missing'] }, min_approved: 2 }, + }; + expect(minVotesForRule(rule, noUsers, noNamed)).toBe(0); + }); + + it('QuorumPercentage ceilings the percentage of the approver pool', () => { + const users = fiveActive(); + // 51% of 5 = 2.55 → ceiling = 3 + const rule: RequestPolicyRule = { + QuorumPercentage: { approvers: { Any: null }, min_approved: 51 }, + }; + expect(minVotesForRule(rule, users, noNamed)).toBe(3); + }); + + it('QuorumPercentage returns 0 when the approver pool is empty', () => { + const rule: RequestPolicyRule = { + QuorumPercentage: { approvers: { Any: null }, min_approved: 100 }, + }; + expect(minVotesForRule(rule, noUsers, noNamed)).toBe(0); + }); + + it('AnyOf returns the minimum over children (easiest path)', () => { + const users = fiveActive(); + const rule: RequestPolicyRule = { + AnyOf: [ + { Quorum: { approvers: { Any: null }, min_approved: 3 } }, + { Quorum: { approvers: { Any: null }, min_approved: 1 } }, + ], + }; + expect(minVotesForRule(rule, users, noNamed)).toBe(1); + }); + + it('AnyOf with no children returns Infinity', () => { + expect(minVotesForRule({ AnyOf: [] }, noUsers, noNamed)).toBe(Number.POSITIVE_INFINITY); + }); + + it('AllOf returns the maximum over children (lower-bound estimate)', () => { + const users = fiveActive(); + const rule: RequestPolicyRule = { + AllOf: [ + { Quorum: { approvers: { Any: null }, min_approved: 2 } }, + { Quorum: { approvers: { Any: null }, min_approved: 4 } }, + ], + }; + expect(minVotesForRule(rule, users, noNamed)).toBe(4); + }); + + it('AllOf with no children returns 0', () => { + expect(minVotesForRule({ AllOf: [] }, noUsers, noNamed)).toBe(0); + }); + + it('Not returns Infinity (opaque)', () => { + const rule: RequestPolicyRule = { + Not: { Quorum: { approvers: { Any: null }, min_approved: 1 } }, + }; + expect(minVotesForRule(rule, noUsers, noNamed)).toBe(Number.POSITIVE_INFINITY); + }); + + it('NamedRule resolves and recurses into the referenced rule', () => { + const users = fiveActive(); + const admin = makeNamedRule({ + id: 'nr-admin', + rule: { Quorum: { approvers: { Any: null }, min_approved: 2 } }, + }); + expect(minVotesForRule({ NamedRule: 'nr-admin' }, users, namedMap([admin]))).toBe(2); + }); + + it('NamedRule pointing at a missing id returns Infinity', () => { + expect(minVotesForRule({ NamedRule: 'nr-missing' }, noUsers, noNamed)).toBe( + Number.POSITIVE_INFINITY, + ); + }); + + it('NamedRule cycle returns Infinity, never infinite-loops', () => { + const a = makeNamedRule({ id: 'nr-a', rule: { NamedRule: 'nr-b' } }); + const b = makeNamedRule({ id: 'nr-b', rule: { NamedRule: 'nr-a' } }); + expect(minVotesForRule({ NamedRule: 'nr-a' }, noUsers, namedMap([a, b]))).toBe( + Number.POSITIVE_INFINITY, + ); + }); + + it('does not consult the UserSpecifier for AutoApproved / AllowListed', () => { + // UserSpecifier is unused for these variants; sanity check. + const _unused: UserSpecifier = { Id: [] }; + expect(minVotesForRule({ AutoApproved: null }, fiveActive(), noNamed)).toBe(0); + void _unused; + }); +}); From 3c673f85586265b45a2a505450aacf5ce54d278f Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 18:07:24 +0200 Subject: [PATCH 06/13] refactor(cli): use agent-js instead of shelling out to dfx for audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback (https://github.com/dfinity/orbit/pull/638#discussion_r3468451929), swap `dfx canister call --output json` for `@dfinity/agent` directly: - station.core: paginated wrappers now drive an `ActorSubclass` built once at audit start. No shell, no string interpolation, no command-injection class. - agent.ts: builds the `HttpAgent` + `Actor`, resolving the network → host mapping (mainnet hardcoded to icp-api.io; other networks fall back to dfx.json so local replicas still work). - identity.ts: loads a plaintext Ed25519 dfx PEM via Node's crypto and the PKCS#8 envelope, hands the raw seed to `Ed25519KeyIdentity.fromSecretKey`. Encrypted PEMs are refused with a clear error pointing operators at the `--storage-mode plaintext` flag. - index.ts: builds the actor once and passes it to each list call. - generated/: vendored copy of `station.did.{js,d.ts,did}` so the audit can type the actor without reaching into apps/wallet. `npm run generate-station-types` script regenerates from `core/station/api/spec.did` via `didc bind`. - README updated to reflect that the dfx CLI is no longer needed at runtime — only the PEM file in dfx's identity-store layout. The 52 unit tests still pass (they mock at the check level, not the actor plumbing). End-to-end verification is the same as before: point at a real station with `--network ic --identity orbit-audit-plaintext`. --- cli/package.json | 3 +- cli/src/audit/README.md | 4 +- cli/src/audit/agent.ts | 48 + cli/src/audit/generated/station.did | 3515 +++++++++++++ cli/src/audit/generated/station.did.d.ts | 5925 ++++++++++++++++++++++ cli/src/audit/generated/station.did.js | 2170 ++++++++ cli/src/audit/identity.ts | 82 + cli/src/audit/index.ts | 16 +- cli/src/audit/station.core.ts | 212 +- 9 files changed, 11862 insertions(+), 113 deletions(-) create mode 100644 cli/src/audit/agent.ts create mode 100644 cli/src/audit/generated/station.did create mode 100644 cli/src/audit/generated/station.did.d.ts create mode 100644 cli/src/audit/generated/station.did.js create mode 100644 cli/src/audit/identity.ts diff --git a/cli/package.json b/cli/package.json index b220f106e..fda473b56 100644 --- a/cli/package.json +++ b/cli/package.json @@ -10,7 +10,8 @@ "start": "node dist/cli.js", "expose": "pnpm link --global", "test": "vitest run", - "generate-types": "didc bind --target ts ../core/control-panel/api/spec.did > ./src/generated/control_panel.d.ts" + "generate-types": "didc bind --target ts ../core/control-panel/api/spec.did > ./src/generated/control_panel.d.ts", + "generate-station-types": "didc bind --target ts ../core/station/api/spec.did > ./src/audit/generated/station.did.d.ts && didc bind --target js ../core/station/api/spec.did > ./src/audit/generated/station.did.js && cp ../core/station/api/spec.did ./src/audit/generated/station.did" }, "devDependencies": { "commander": "12.1.0", diff --git a/cli/src/audit/README.md b/cli/src/audit/README.md index a84510ea6..32bb399fd 100644 --- a/cli/src/audit/README.md +++ b/cli/src/audit/README.md @@ -1,6 +1,8 @@ # `orbit-cli audit` -Read-only sanity checks against an Orbit station's configuration. The command pulls live state via the station's `list_*` query methods, runs a set of static checks against the configuration, and prints a severity-sorted report. Nothing is mutated — the audit is safe to run at any time, against any station the caller has read access to. +Read-only sanity checks against an Orbit station's configuration. The command pulls live state via the station's `list_*` query methods using `agent-js`, runs a set of static checks against the configuration, and prints a severity-sorted report. Nothing is mutated — the audit is safe to run at any time, against any station the caller has read access to. + +> **Identity prerequisite.** The audit signs requests with an Ed25519 dfx identity loaded from `~/.config/dfx/identity//identity.pem`. The `dfx` CLI itself does not need to be on PATH at runtime, but the identity store layout follows dfx's convention. Passphrase-protected identities aren't supported; create a plaintext one for the audit if needed (`dfx identity new orbit-audit --storage-mode plaintext`). ## Usage diff --git a/cli/src/audit/agent.ts b/cli/src/audit/agent.ts new file mode 100644 index 000000000..e7d675282 --- /dev/null +++ b/cli/src/audit/agent.ts @@ -0,0 +1,48 @@ +import { Actor, ActorSubclass, HttpAgent, Identity } from '@dfinity/agent'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import { ROOT_PATH } from '../utils'; +// `idlFactory` is the runtime IDL constructor generated from `core/station/api/spec.did`. +// The file is a regenerable mirror of `apps/wallet/src/generated/station/station.did.js`. +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore - generated .js file without accompanying types in this directory +import { idlFactory } from './generated/station.did.js'; + +const MAINNET_HOSTS: Record = { + ic: 'https://icp-api.io', +}; + +const resolveHost = async (network: string): Promise => { + if (MAINNET_HOSTS[network]) return MAINNET_HOSTS[network]; + + // Fall back to dfx.json for local-replica style networks. + const dfxJsonPath = join(ROOT_PATH, 'dfx.json'); + const raw = await readFile(dfxJsonPath, 'utf8').catch(() => null); + if (!raw) { + throw new Error(`Unknown network '${network}' and dfx.json not found at ${dfxJsonPath}.`); + } + const dfxJson = JSON.parse(raw) as { + networks?: Record; + }; + const cfg = dfxJson.networks?.[network]; + if (!cfg) { + throw new Error(`Network '${network}' is not defined in dfx.json.`); + } + if (cfg.providers && cfg.providers.length > 0) return cfg.providers[0]; + if (cfg.bind) return cfg.bind.startsWith('http') ? cfg.bind : `http://${cfg.bind}`; + throw new Error(`Network '${network}' has no providers or bind URL configured.`); +}; + +export const createStationActor = async ( + canisterId: string, + network: string, + identity: Identity, +): Promise => { + const host = await resolveHost(network); + const agent = new HttpAgent({ host, identity }); + if (network !== 'ic') { + // Local replicas need their root key fetched to verify certificates. + await agent.fetchRootKey(); + } + return Actor.createActor(idlFactory, { agent, canisterId }); +}; diff --git a/cli/src/audit/generated/station.did b/cli/src/audit/generated/station.did new file mode 100644 index 000000000..369776c83 --- /dev/null +++ b/cli/src/audit/generated/station.did @@ -0,0 +1,3515 @@ +// The asset symbol, e.g. "ICP" or "BTC". +type AssetSymbol = text; +// The network id, represented by the blockchain symbol and network name (e.g. "icp:mainnet"). +type NetworkId = text; +// Most ids under the station canister are in the UUID format (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). +type UUID = text; +// The timestamp type used in the canister. +type TimestampRFC3339 = text; +// The hash string representation for sha256. +type Sha256Hash = text; + +type PaginationInput = record { + // The offset to use for pagination. + offset : opt nat64; + // The maximum number of items to retrieve. + // + // If not set, the default limit will be used. + limit : opt nat16; +}; + +// A record type that can be used to represent the caller privileges for a given request policy. +type RequestPolicyCallerPrivileges = record { + // The request policy id. + id : UUID; + // Whether or not the caller can edit the resource. + can_edit : bool; + // Whether or not the caller can delete the resource. + can_delete : bool; +}; + +// Represents a request policy with the associated rule. +type RequestPolicy = record { + id : UUID; + specifier : RequestSpecifier; + rule : RequestPolicyRule; +}; + +// Defines the various types of requests that can be created. +type RequestSpecifier = variant { + AddAccount; + AddUser; + EditAccount : ResourceIds; + EditUser : ResourceIds; + Transfer : ResourceIds; + AddAddressBookEntry; + EditAddressBookEntry : ResourceIds; + RemoveAddressBookEntry : ResourceIds; + SystemUpgrade; + SetDisasterRecovery; + ChangeExternalCanister : ExternalCanisterId; + FundExternalCanister : ExternalCanisterId; + CreateExternalCanister; + CallExternalCanister : CallExternalCanisterResourceTarget; + EditPermission : ResourceSpecifier; + AddRequestPolicy; + EditRequestPolicy : ResourceIds; + RemoveRequestPolicy : ResourceIds; + AddUserGroup; + EditUserGroup : ResourceIds; + RemoveUserGroup : ResourceIds; + ManageSystemInfo; + AddAsset; + EditAsset : ResourceIds; + RemoveAsset : ResourceIds; + AddNamedRule; + EditNamedRule : ResourceIds; + RemoveNamedRule : ResourceIds; +}; + +// A record type that can be used to represent a percentage of users that are required to approve a rule. +type QuorumPercentage = record { + // The users that are required to approve the request. + approvers : UserSpecifier; + // The required user approvals for the rule to be approved. + min_approved : nat16; +}; + +// A record type that can be used to represent the minimum quorum of users that are required to approve a rule. +type Quorum = record { + // The users that can approve the request. + approvers : UserSpecifier; + // The minimum number of user approvals required for the rule to be approved. + min_approved : nat16; +}; + +type RequestPolicyRuleInput = variant { + Remove; + Set : RequestPolicyRule; +}; + +// Defines the various types rules that can be used in a request evaluation. +type RequestPolicyRule = variant { + AutoApproved; + QuorumPercentage : QuorumPercentage; + Quorum : Quorum; + AllowListedByMetadata : AddressBookMetadata; + AllowListed; + AnyOf : vec RequestPolicyRule; + AllOf : vec RequestPolicyRule; + Not : RequestPolicyRule; + NamedRule : UUID; +}; + +// Defines the high level result of evaluating a request policy rule. +type EvaluationStatus = variant { + Approved; + Rejected; + Pending; +}; + +// Defines the evaluation data of a request policy rule. +type EvaluatedRequestPolicyRule = variant { + AutoApproved; + QuorumPercentage : record { + min_approved : nat64; + total_possible_approvers : nat64; + approvers : vec UUID; + }; + Quorum : record { + min_approved : nat64; + total_possible_approvers : nat64; + approvers : vec UUID; + }; + AllowListedByMetadata : record { + metadata : AddressBookMetadata; + }; + AllowListed; + AnyOf : vec RequestPolicyRuleResult; + AllOf : vec RequestPolicyRuleResult; + Not : RequestPolicyRuleResult; +}; + +// A record type representing the full evaluation result of a request policy rule. +type RequestPolicyRuleResult = record { + // The final evaluation status of the rule. + status : EvaluationStatus; + // The result of the evaluation of the rule and all its sub-rules. + evaluated_rule : EvaluatedRequestPolicyRule; +}; + +// List of reasons why a request can be approved or rejected. +type EvaluationSummaryReason = variant { + ApprovalQuorum; + AllowList; + AllowListMetadata; + AutoApproved; +}; + +// A record type representing the full evaluation result of all matching policies for a request. +type RequestEvaluationResult = record { + // The request id that was evaluated. + request_id : UUID; + // The final evaluation status of the request. + status : EvaluationStatus; + // The evaluation results of all matching policies. + policy_results : vec RequestPolicyRuleResult; + // The reasons why the request was approved or rejected. + result_reasons : opt vec EvaluationSummaryReason; +}; + +// Defines a user in the context of a request. +type UserSpecifier = variant { + Any; + Id : vec UUID; + Group : vec UUID; +}; + +type ResourceSpecifier = variant { + Any; + Resource : Resource; +}; + +// Defines the various states that a notification can be in. +type NotificationStatus = variant { + // The notification has been sent. + Sent; + // The notification has been read by the user. + Read; +}; + +// Represents the different types of notifications within the system. +type NotificationType = variant { + // Notification for system-wide messages. + // This can be used for announcements, scheduled maintenance reminders, or other important system messages. + SystemMessage; + // Notification for the creation of a new request. + // This should be used to alert users when a new request that requires their attention has been created. + RequestCreated : record { + // The request id that was created. + request_id : UUID; + // The type of the request (e.g. "transfer"). + operation_type : RequestOperationType; + // Account id is available for relevant request types. + account_id : opt UUID; + // User id is available for relevant request types. + user_id : opt UUID; + }; + // Notification for the failure of a request. + // This should be used to alert the requester when a request has failed to be executed. + RequestFailed : record { + // The request id that was created. + request_id : UUID; + // The type of the request (e.g. "transfer"). + operation_type : RequestOperationType; + // Details about the failure. + reason : opt text; + }; + + // Notification for the rejection of a request. + // This should be used to alert the requester when a request has been rejected. + RequestRejected : record { + // The request id that was created. + request_id : UUID; + // The type of the request (e.g. "transfer"). + operation_type : RequestOperationType; + // List of reasons why the request was rejected. + reasons : opt vec EvaluationSummaryReason; + }; +}; + +type NotificationTypeInput = variant { + SystemMessage; + RequestCreated; +}; + +// A record type that can be used to represent a notification. +type Notification = record { + // The notification id which is a UUID (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + id : UUID; + // The type of the notification. + notification_type : NotificationType; + // The notification status. + status : NotificationStatus; + // The user that the notification is for. + target_user_id : UUID; + // The notification title in a single locale. + title : text; + // The notification message in a single locale. + message : opt text; + // The time at which the notification was created. + created_at : TimestampRFC3339; +}; + +// The input type for getting the list of notifications associated with the caller. +type ListNotificationsInput = record { + // Show only notifications with the given status. + status : opt NotificationStatus; + // The type of the notification (e.g. "system-message"). + notification_type : opt NotificationTypeInput; + // From which created time to retrieve the notifications. + from_dt : opt TimestampRFC3339; + // Until which created time to retrieve the notifications. + to_dt : opt TimestampRFC3339; +}; + +// The result type for getting the list of notifications. +type ListNotificationsResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of notifications ordered by creation time (newest first). + notifications : vec Notification; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +type MarkNotificationsReadInput = record { + // The notifications to mark as read. + notification_ids : vec UUID; + // If true, all notifications will be marked as read. + read : bool; +}; + +type MarkNotificationReadResult = variant { + Ok; + Err : Error; +}; + +// The status of a request. +type RequestStatus = variant { + Created; + Approved; + Rejected; + Cancelled : record { + reason : opt text; + }; + Scheduled : record { + scheduled_at : TimestampRFC3339; + }; + Processing : record { + started_at : TimestampRFC3339; + }; + Completed : record { + completed_at : TimestampRFC3339; + }; + Failed : record { + reason : opt text; + }; +}; + +// The status code of a request. +type RequestStatusCode = variant { + Created; + Approved; + Rejected; + Cancelled; + Scheduled; + Processing; + Completed; + Failed; +}; + +// The status of a request. +type RequestApprovalStatus = variant { Approved; Rejected }; + +// A record type that is used to represent a user approval decision on a request. +type RequestApproval = record { + // The user that has recorded the approval decision. + approver_id : UUID; + // The user has added to the request, once provided it cannot be changed. + status : RequestApprovalStatus; + // Optional reason for the decision. + status_reason : opt text; + // The time at which the decision was made. + decided_at : TimestampRFC3339; +}; + +// Input type for transferring funds. +type TransferOperationInput = record { + // The account id to use for the transaction. + from_account_id : UUID; + // The asset id to transfer. + from_asset_id : UUID; + // The standard to use for the transfer. + with_standard : text; + // The amount to transfer. + amount : nat; + // The destination address of the transaction (e.g. "1BvBMSE..."). + to : text; + // The fee to pay for the transaction, if applicable. + // + // If not set, the default fee will be used. + fee : opt nat; + // The network to use for the transaction, if not the + // default network of the account will be used. + network : opt Network; + // Transactions can be tagged with an optional additional info + // (e.g. a nonce in the case of an Ethereum transaction) + metadata : vec TransferMetadata; +}; + +// Input type for transferring funds. +type TransferOperation = record { + // The account to use for the transaction. + from_account : opt Account; + // The asset to use for the transaction. + from_asset : Asset; + // The network to use for the transaction. + network : Network; + // The input to the request to transfer funds. + input : TransferOperationInput; + // The id of the executed transfer. + transfer_id : opt UUID; + // The fee paid for the transaction. + fee : opt nat; +}; + +// Mutate the list of assets. +type ChangeAssets = variant { + // Replace all current assets with the specified list. + ReplaceWith : record { + assets : vec UUID; + }; + // Change the list of assets by adding and removing assets. + Change : record { + add_assets : vec UUID; + remove_assets : vec UUID; + }; +}; + +// Input type for editing an account through a request. +type EditAccountOperationInput = record { + // The account id that will be edited. + account_id : UUID; + // A friendly name for the account (e.g. "My Account"). + name : opt text; + // Mutate the list of assets. + change_assets : opt ChangeAssets; + // Who can read the account information. + read_permission : opt Allow; + // Who can request configuration changes to the account. + configs_permission : opt Allow; + // Who can request transfers from the account. + transfer_permission : opt Allow; + // The request policy for what it takes to execute a configuration change. + configs_request_policy : opt RequestPolicyRuleInput; + // The request policy for what it takes to execute a transfer. + transfer_request_policy : opt RequestPolicyRuleInput; +}; + +type EditAccountOperation = record { + // The input to the request to edit the account. + input : EditAccountOperationInput; +}; + +// Input type for adding an account through a request. +type AddAccountOperationInput = record { + // A friendly name for the account (e.g. "My Account"). + name : text; + // The assets to add to the account. + assets : vec UUID; + // Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`). + metadata : vec AccountMetadata; + // Who can read the account information. + read_permission : Allow; + // Who can request updates to the account. + configs_permission : Allow; + // Who can request transfers from the account. + transfer_permission : Allow; + // The approval policy for updates to the account. + configs_request_policy : opt RequestPolicyRule; + // The approval policy for transfers from the account. + transfer_request_policy : opt RequestPolicyRule; +}; + +type AddAccountOperation = record { + // The account, only available after the request is executed. + account : opt Account; + // The input to the request to add the account. + input : AddAccountOperationInput; +}; + +type AddAddressBookEntryOperation = record { + // The address book entry, only available after the request is executed. + address_book_entry : opt AddressBookEntry; + // The input to the request to add the address book entry. + input : AddAddressBookEntryOperationInput; +}; + +// Input type for creating a new address book entry through a request. +type AddAddressBookEntryOperationInput = record { + // The owner of the address. + address_owner : text; + // The actual address. + address : text; + // The format of the address, eg. icp_account_identifier + address_format : text; + // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + blockchain : text; + // Metadata associated with the address book entry (e.g. `{"kyc": "true"}`). + metadata : vec AddressBookMetadata; + // The labels associated with the address book entry (e.g. `["exchange", "kyc"]`). + labels : vec text; +}; + +type EditAddressBookEntryOperation = record { + // The input to the request to edit the address book entry. + input : EditAddressBookEntryOperationInput; +}; + +// Type for instructions to update the address book entry's metadata. +type ChangeAddressBookMetadata = variant { + // Replace all existing metadata by the specified metadata. + ReplaceAllBy : vec AddressBookMetadata; + // Override values of existing metadata with the specified keys + // and add new metadata if no metadata can be found with the specified keys. + OverrideSpecifiedBy : vec AddressBookMetadata; + // Remove metadata with the specified keys. + RemoveKeys : vec text; +}; + +// Input type for updating an address book entry through a request. +type EditAddressBookEntryOperationInput = record { + // The id of the address book entry. + address_book_entry_id : UUID; + // The new owner of the address. + address_owner : opt text; + // The updated list of labels associated with the address book entry. + labels : opt vec text; + // Instructions to update the address book entry's metadata. + change_metadata : opt ChangeAddressBookMetadata; +}; + +type RemoveAddressBookEntryOperation = record { + // The input to the request to remove the address book entry. + input : RemoveAddressBookEntryOperationInput; +}; + +// Input type for removing an address book entry through a request. +type RemoveAddressBookEntryOperationInput = record { + // The id of the address book entry. + address_book_entry_id : UUID; +}; + +type AddUserOperationInput = record { + // The user name (e.g. "John Doe"). + name : text; + // The principals associated with the user. + identities : vec principal; + // The list of groups the user belongs to. + // + // Users can be tagged with groups that can be used to control access to the station + // (e.g. the UUID of the finance group). + groups : vec UUID; + // The status of the user (e.g. `Active`). + // + // The user must be active to be able to practically use the station. + status : UserStatus; +}; + +type AddUserOperation = record { + // The user that was added, only available after the request is executed. + user : opt User; + // The input to the request to add the user. + input : AddUserOperationInput; +}; + +type EditUserOperationInput = record { + // The id of the user to edit. + id : UUID; + // The user name (e.g. "John Doe"). + name : opt text; + // The principals associated with the user. + identities : opt vec principal; + // The list of groups the user belongs to. + // + // Users can be tagged with groups that can be used to control access to the station + // (e.g. "UUID of the finance group"). + groups : opt vec UUID; + // The status of the user (e.g. `Active`). + status : opt UserStatus; + // Cancel all pending (request status `Created`) requests for this user. + cancel_pending_requests : opt bool; +}; + +type EditUserOperation = record { + // The input to the request to edit the user. + input : EditUserOperationInput; +}; + +type AddUserGroupOperationInput = record { + // The name of the group. + name : text; +}; + +type AddUserGroupOperation = record { + // The user group that was added, only available after the request is executed. + user_group : opt UserGroup; + // The input to the request to add the user group. + input : AddUserGroupOperationInput; +}; + +type EditUserGroupOperationInput = record { + // The id of the group to edit. + user_group_id : UUID; + // The name of the group. + name : text; +}; + +type EditUserGroupOperation = record { + // The input to the request to edit the user group. + input : EditUserGroupOperationInput; +}; + +type RemoveUserGroupOperationInput = record { + // The id of the group to remove. + user_group_id : UUID; +}; + +type RemoveUserGroupOperation = record { + // The input to the request to remove the user group. + input : RemoveUserGroupOperationInput; +}; + +type CanisterInstallMode = variant { + install; + reinstall; + // Upgrade an existing canister. The optional record mirrors the IC + // management canister's `CanisterUpgradeOptions`. + // `wasm_memory_persistence = keep` is required for Motoko canisters that + // use Enhanced Orthogonal Persistence; otherwise the IC clears their main + // memory. + upgrade : opt record { + skip_pre_upgrade : opt bool; + wasm_memory_persistence : opt variant { + keep; + replace; + }; + }; +}; + +type SystemUpgradeTarget = variant { + UpgradeStation; + UpgradeUpgrader; +}; + +type WasmModuleExtraChunks = record { + // The asset canister from which the chunks are to be retrieved. + store_canister : principal; + // The name of the asset containing extra chunks in the asset canister. + extra_chunks_key : text; + // The hash of the assembled wasm module. + wasm_module_hash : blob; +}; + +type SystemUpgradeOperationInput = record { + // The target to change. + target : SystemUpgradeTarget; + // The wasm module to install. + module : blob; + // Additional wasm module chunks to append to the wasm module. + module_extra_chunks : opt WasmModuleExtraChunks; + // The initial argument passed to the new wasm module. + arg : opt blob; + // Determines whether a backup snapshot should be taken (before the upgrade). + // If so and the maximum number of backup snapshots is reached, + // then the oldest backup snapshot is atomically replaced + // by the new backup snapshot. + take_backup_snapshot : opt bool; +}; + +type SystemUpgradeOperation = record { + // The target to change. + target : SystemUpgradeTarget; + // The checksum of the wasm module. + module_checksum : Sha256Hash; + // The checksum of the arg blob. + arg_checksum : opt Sha256Hash; + // Determines whether a backup snapshot should be taken (before the upgrade). + // If so and the maximum number of backup snapshots is reached, + // then the oldest backup snapshot is atomically replaced + // by the new backup snapshot. + take_backup_snapshot : opt bool; +}; + +type SystemRestoreTarget = variant { + RestoreStation; + RestoreUpgrader; +}; + +type SystemRestoreOperationInput = record { + // The target to restore from a snapshot. + target : SystemRestoreTarget; + // A snapshot to be restored. + snapshot_id : text; +}; + +type SystemRestoreOperation = record { + input: SystemRestoreOperationInput; +}; + +type DisasterRecoveryCommittee = record { + // The user group id of the committee. + user_group_id : UUID; + // The quorum required for the committee to approve a disaster recovery operation. + quorum : nat16; +}; + +type SetDisasterRecoveryOperationInput = record { + // The disaster recovery committee. + committee : opt DisasterRecoveryCommittee; +}; + +type SetDisasterRecoveryOperation = record { + // The disaster recovery committee. + committee : opt DisasterRecoveryCommittee; +}; + +type ChangeExternalCanisterOperationInput = record { + // The canister to install. + canister_id : principal; + // The canister installation mode. + mode : CanisterInstallMode; + // The wasm module to install. + module : blob; + // Additional wasm module chunks to append to the wasm module. + module_extra_chunks : opt WasmModuleExtraChunks; + // The initial argument passed to the new wasm module. + arg : opt blob; +}; + +type ChangeExternalCanisterOperation = record { + // The canister to install. + canister_id : principal; + // The canister installation mode. + mode : CanisterInstallMode; + // The checksum of the wasm module. + module_checksum : Sha256Hash; + // The checksum of the arg blob. + arg_checksum : opt Sha256Hash; +}; + +type SubnetFilter = record { + subnet_type : opt text; +}; + +type SubnetSelection = variant { + // Choose a specific subnet + Subnet : record { + subnet : principal; + }; + // Choose a random subnet that fulfills the specified properties + Filter : SubnetFilter; +}; + +type CreateExternalCanisterOperationKindCreateNew = record { + // The initial cycles to allocate to the canister. + // + // If not set, only the minimal amount of cycles required to create the + // canister will be allocated. + initial_cycles : opt nat64; + + // The subnet on which the canister should be created. + // + // By default, the canister is created on the same subnet as the station. + subnet_selection : opt SubnetSelection; +}; + +type CreateExternalCanisterOperationKindAddExisting = record { + // The canister id to use. + canister_id : principal; +}; + +type CreateExternalCanisterOperationKind = variant { + // A new canister is created. + CreateNew : CreateExternalCanisterOperationKindCreateNew; + // An existing canister is added to the station. + AddExisting : CreateExternalCanisterOperationKindAddExisting; +}; + +// ExternalCanister can have additional information attached to them, +// this type can be used to represent the additional info. +type ExternalCanisterMetadata = record { + // The key of the additional info (e.g. "app_id") + key : text; + // The value of the additional info (e.g. "2ec270f1-7663-4d51-b70f-9339486b6d6d") + value : text; +}; + +type CreateExternalCanisterOperationInput = record { + // The kind of create operation to perform. + kind : CreateExternalCanisterOperationKind; + // The name of the external canister. + name : text; + // The description of the external canister. + description : opt text; + // The labels of the external canister. + labels : opt vec text; + // The metadata of the external canister. + metadata : opt vec ExternalCanisterMetadata; + // What operations are allowed on the canister. + permissions : ExternalCanisterPermissionsCreateInput; + // The request policies for the canister. + request_policies : ExternalCanisterRequestPoliciesCreateInput; +}; + +// Type for instructions to update the external canister's metadata. +type ChangeExternalCanisterMetadata = variant { + // Replace all existing metadata by the specified metadata. + ReplaceAllBy : vec ExternalCanisterMetadata; + // Override values of existing metadata with the specified keys + // and add new metadata if no metadata can be found with the specified keys. + OverrideSpecifiedBy : vec ExternalCanisterMetadata; + // Remove metadata with the specified keys. + RemoveKeys : vec text; +}; + +type ConfigureExternalCanisterSettingsInput = record { + name : opt text; + // The description of the external canister. + description : opt text; + // The labels of the external canister. + labels : opt vec text; + // The metadata of the external canister. + change_metadata : opt ChangeExternalCanisterMetadata; + // What operations are allowed on the canister. + permissions : opt ExternalCanisterPermissionsUpdateInput; + // The request policies for the canister. + request_policies : opt ExternalCanisterRequestPoliciesUpdateInput; + // The state of the external canister. + state : opt ExternalCanisterState; +}; + +// The input type for configuring an external canister in the station. +type ConfigureExternalCanisterOperationKind = variant { + // The settings to configure for the external canister. + Settings : ConfigureExternalCanisterSettingsInput; + // Remove the canister from the Station only. + SoftDelete; + // Remove the canister from the Station and the IC. + // + // Caution: This operation is irreversible. + Delete; + // The Internet Computer canister settings to configure for the external canister. + NativeSettings : DefiniteCanisterSettingsInput; +}; + +type ConfigureExternalCanisterOperationInput = record { + // The canister to update. + canister_id : principal; + // The kind of operation to perform. + kind : ConfigureExternalCanisterOperationKind; +}; + +type ConfigureExternalCanisterOperation = ConfigureExternalCanisterOperationInput; + +// The operation kind for funding an external canister in the station. +type FundExternalCanisterOperationKind = variant { + // The amount of cycles to send to the canister. + Send : FundExternalCanisterSendCyclesInput; +}; + +// The input type for specifying the cycles to send to an external canister. +type FundExternalCanisterSendCyclesInput = record { + // The amount of cycles to send to the canister. + cycles : nat64; +}; + +// The input type for funding an external canister in the station. +type FundExternalCanisterOperationInput = record { + // The external canister to fund. + canister_id : principal; + // The kind of funding operation to perform. + kind : FundExternalCanisterOperationKind; +}; + +// The request operation for funding an external canister from the station. +type FundExternalCanisterOperation = FundExternalCanisterOperationInput; + +type MonitoringExternalCanisterCyclesThresholdInput = record { + /// The min cycles threshold to trigger the funding operation. + min_cycles : nat; + /// The cycles to fund the canister with when the threshold is triggered. + fund_cycles : nat; +}; + +type MonitoringExternalCanisterEstimatedRuntimeInput = record { + /// The estimated min runtime in seconds to trigger the funding operation. + min_runtime_secs : nat64; + /// The runtime seconds to add to the estimated runtime. + fund_runtime_secs : nat64; + /// The maximum cycles to fund the canister with, only used when the estimated runtime is available. + max_runtime_cycles_fund : nat; + /// The fallback min cycles to trigger the funding operation when the estimated runtime is not available, + /// or the cycles balance is below the threshold. + fallback_min_cycles : nat; + /// The fallback cycles to fund the canister with when the estimated runtime is not available, + /// or the cycles balance is below the threshold. + fallback_fund_cycles : nat; +}; + +// The input type for specifying the strategy for monitoring an external canister. +type MonitorExternalCanisterStrategyInput = variant { + // Fund the canister when the balance is below the threshold. + BelowThreshold : MonitoringExternalCanisterCyclesThresholdInput; + // Fund the canister based on the estimated run time in seconds. + BelowEstimatedRuntime : MonitoringExternalCanisterEstimatedRuntimeInput; + // Fund the canister at a fixed interval with the specified amount of cycles. + Always : nat; +}; + +// The input type for specifying the strategy for monitoring an external canister. +type MonitorExternalCanisterStartInput = record { + // The strategy for funding the canister. + funding_strategy : MonitorExternalCanisterStrategyInput; + // The strategy for obtaining cycles for the funding operation. + cycle_obtain_strategy : opt CycleObtainStrategyInput; +}; + +// The operation kind for monitoring an external canister in the station. +type MonitorExternalCanisterOperationKind = variant { + Start : MonitorExternalCanisterStartInput; + Stop; +}; + +// The input type for monitoring an external canister in the station. +type MonitorExternalCanisterOperationInput = record { + // The external canister to monitor. + canister_id : principal; + // The kind of funding operation to perform. + kind : MonitorExternalCanisterOperationKind; +}; + +// The request operation for monitoring an external canister from the station. +type MonitorExternalCanisterOperation = MonitorExternalCanisterOperationInput; + +type CreateExternalCanisterOperation = record { + canister_id : opt principal; + input : CreateExternalCanisterOperationInput; +}; + +type CanisterMethod = record { + // The canister to call. + canister_id : principal; + // The method to call on the canister. + method_name : text; +}; + +type CallExternalCanisterOperationInput = record { + // The canister method validating the argument blob: + // - on validation success, returns a human-readable rendering of the argument blob + // and then the request becomes `Created`; + // - on validation error, returns a textual diagnostic message + // and then the request creation fails with a validation error + // containing the textual diagnostic message. + // Formally, the return type of the validation method must be + // ``` + // variant { + // Ok : text; + // Err : text; + // } + // ``` + // If omitted (`validation_method = null`), no validation of the argument blob is performed + // and no human-readable rendering of the argument blob is provided. + validation_method : opt CanisterMethod; + // The canister method that is called after the request becomes `Approved` + // passing the validated argument blob. + execution_method : CanisterMethod; + // The argument blob passed to both the validation and execution method. + // Defaults to the candid encoding of '()' if omitted. + arg : opt blob; + // The amount of cycles attached to the call of the execution method. + execution_method_cycles : opt nat64; +}; + +type CallExternalCanisterOperation = record { + // see `CallExternalCanisterOperationInput` + validation_method : opt CanisterMethod; + // see `CallExternalCanisterOperationInput` + execution_method : CanisterMethod; + // The checksum of the argument blob passed to both the validation and execution method. + // Defaults to `null` if no argument blob is provided. + arg_checksum : opt Sha256Hash; + // A human-readable rendering of the argument blob procuded by the validation method. + arg_rendering : opt text; + // The amount of cycles attached to the call of the execution method. + execution_method_cycles : opt nat64; + // The reply blob produced by a successful call of the execution method, + // i.e., when the request is `Completed`. + execution_method_reply : opt blob; + // This field is not populated in list responses, only when using `get_request` and + // setting `with_full_info` to `opt true` to avoid going over the response size limit. + arg : opt blob; +}; + +type SnapshotExternalCanisterOperationInput = record { + // The canister to snapshot. + canister_id : principal; + // A snapshot to be replaced. + replace_snapshot : opt text; + // Should a snapshot be taken if the external canister fails to stop. + force : bool; +}; + +type SnapshotExternalCanisterOperation = record { + input : SnapshotExternalCanisterOperationInput; + // The snapshot id of the new snapshot. + snapshot_id : opt text; +}; + +type RestoreExternalCanisterOperationInput = record { + // The canister to restore from a snapshot. + canister_id : principal; + // A snapshot to be restored. + snapshot_id : text; +}; + +type RestoreExternalCanisterOperation = record { + input : RestoreExternalCanisterOperationInput; +}; + +type PruneExternalCanisterOperationInput = record { + // The canister to prune. + canister_id : principal; + // The resource to prune. + prune : variant { + chunk_store; + snapshot : text; + state; + }; +}; + +type PruneExternalCanisterOperation = record { + input : PruneExternalCanisterOperationInput; +}; + +type EditPermissionOperationInput = record { + // The updated resource that this policy will apply to. + resource : Resource; + // The updated authorization scope for the resource. + auth_scope : opt AuthScope; + // The updated list of users that have access to the resource. + users : opt vec UUID; + // The updated list of user groups that have access to the resource. + user_groups : opt vec UUID; +}; + +type EditPermissionOperation = record { + // The input to the request to edit an permission. + input : EditPermissionOperationInput; +}; + +type AddRequestPolicyOperationInput = record { + // The request specifier that identifies the request to add a policy for. + specifier : RequestSpecifier; + // The rule to use for the request evaluation. + rule : RequestPolicyRule; +}; + +type AddRequestPolicyOperation = record { + // The request policy that was created by the request (only available after the request is executed). + policy_id : opt UUID; + // The input to the request to add a request policy. + input : AddRequestPolicyOperationInput; +}; + +type EditRequestPolicyOperationInput = record { + // The request policy id that will be edited. + policy_id : UUID; + // The updated request specifier that identifies the request to add a policy for. + specifier : opt RequestSpecifier; + // The updated rule to use for the request evaluation. + rule : opt RequestPolicyRule; +}; + +type EditRequestPolicyOperation = record { + // The input to the request to edit a request policy. + input : EditRequestPolicyOperationInput; +}; + +type RemoveRequestPolicyOperationInput = record { + // The request policy id that will be removed. + policy_id : UUID; +}; + +type RemoveRequestPolicyOperation = record { + // The input to the request to remove a request policy. + input : RemoveRequestPolicyOperationInput; +}; + +type RequestOperation = variant { + // A new transfer of funds from a given account. + Transfer : TransferOperation; + // An operation for updating information of an account. + EditAccount : EditAccountOperation; + // An operation for creating a new account. + AddAccount : AddAccountOperation; + // An operation for adding a new user. + AddUser : AddUserOperation; + // An operation for editing an existing user. + EditUser : EditUserOperation; + // An operation for creating a new address book entry. + AddAddressBookEntry : AddAddressBookEntryOperation; + // An operation for updating an existing address book entry. + EditAddressBookEntry : EditAddressBookEntryOperation; + // An operation for removing an existing address book entry. + RemoveAddressBookEntry : RemoveAddressBookEntryOperation; + // An operation for adding a new user group. + AddUserGroup : AddUserGroupOperation; + // An operation for editing an existing user group. + EditUserGroup : EditUserGroupOperation; + // An operation for removing an existing user group. + RemoveUserGroup : RemoveUserGroupOperation; + // An operation for performing a system upgrade on the station or upgrader. + SystemUpgrade : SystemUpgradeOperation; + // An operation for restoring the station or upgrader from a snapshot. + SystemRestore : SystemRestoreOperation; + // An operation for setting disaster recovery. + SetDisasterRecovery : SetDisasterRecoveryOperation; + // An operation for changing a external canister. + ChangeExternalCanister : ChangeExternalCanisterOperation; + // An operation for creating a external canister. + CreateExternalCanister : CreateExternalCanisterOperation; + // An operation for configuring an external canister. + ConfigureExternalCanister : ConfigureExternalCanisterOperation; + // An operation for funding an external canister. + FundExternalCanister : FundExternalCanisterOperation; + // An operation for monitoring an external canister. + MonitorExternalCanister : MonitorExternalCanisterOperation; + // An operation for calling an external canister. + CallExternalCanister : CallExternalCanisterOperation; + // An operation for snapshotting an external canister. + SnapshotExternalCanister : SnapshotExternalCanisterOperation; + // An operation for restoring an external canister from a snapshot. + RestoreExternalCanister : RestoreExternalCanisterOperation; + // An operation for pruning an external canister. + PruneExternalCanister : PruneExternalCanisterOperation; + // An operation for editing an permission. + EditPermission : EditPermissionOperation; + // An operation for adding a request policy. + AddRequestPolicy : AddRequestPolicyOperation; + // An operation for editing a request policy. + EditRequestPolicy : EditRequestPolicyOperation; + // An operation for removing a request policy. + RemoveRequestPolicy : RemoveRequestPolicyOperation; + // An operation for managing system info. + ManageSystemInfo : ManageSystemInfoOperation; + // An operation for adding a new asset. + AddAsset : AddAssetOperation; + // An operation for editing an existing asset. + EditAsset : EditAssetOperation; + // An operation for removing an existing asset. + RemoveAsset : RemoveAssetOperation; + // An operation for adding a new named rule. + AddNamedRule : AddNamedRuleOperation; + // An operation for editing an existing named rule. + EditNamedRule : EditNamedRuleOperation; + // An operation for removing an existing named rule. + RemoveNamedRule : RemoveNamedRuleOperation; +}; + +type RequestOperationInput = variant { + // A new transfer of funds from a given account. + Transfer : TransferOperationInput; + // An operation for updating information of an account. + EditAccount : EditAccountOperationInput; + // An operation for adding a new account. + AddAccount : AddAccountOperationInput; + // An operation for adding a new user. + AddUser : AddUserOperationInput; + // An operation for editing an existing user. + EditUser : EditUserOperationInput; + // An operation for creating a new address book entry. + AddAddressBookEntry : AddAddressBookEntryOperationInput; + // An operation for updating an address book entry. + EditAddressBookEntry : EditAddressBookEntryOperationInput; + // An operation for removing an address book entry. + RemoveAddressBookEntry : RemoveAddressBookEntryOperationInput; + // An operation for adding a new user group. + AddUserGroup : AddUserGroupOperationInput; + // An operation for editing an existing user group. + EditUserGroup : EditUserGroupOperationInput; + // An operation for removing an existing user group. + RemoveUserGroup : RemoveUserGroupOperationInput; + // An operation for performing a system upgrade on the station or upgrader. + SystemUpgrade : SystemUpgradeOperationInput; + // An operation for restoring the station or upgrader from a snapshot. + SystemRestore : SystemRestoreOperationInput; + // An operation for setting disaster recovery. + SetDisasterRecovery : SetDisasterRecoveryOperationInput; + // An operation for changing a external canister. + ChangeExternalCanister : ChangeExternalCanisterOperationInput; + // An operation for creating a external canister. + CreateExternalCanister : CreateExternalCanisterOperationInput; + // An operation for configuring an external canister. + ConfigureExternalCanister : ConfigureExternalCanisterOperationInput; + // An operation for calling an external canister. + CallExternalCanister : CallExternalCanisterOperationInput; + // An operation for funding an external canister. + FundExternalCanister : FundExternalCanisterOperationInput; + // An operation for monitoring an external canister. + MonitorExternalCanister : MonitorExternalCanisterOperationInput; + // An operation for snapshotting an external canister. + SnapshotExternalCanister : SnapshotExternalCanisterOperationInput; + // An operation for restoring an external canister from a snapshot. + RestoreExternalCanister : RestoreExternalCanisterOperationInput; + // An operation for pruning an external canister. + PruneExternalCanister : PruneExternalCanisterOperationInput; + // An operation for editing an permission. + EditPermission : EditPermissionOperationInput; + // An operation for adding a request policy. + AddRequestPolicy : AddRequestPolicyOperationInput; + // An operation for editing a request policy. + EditRequestPolicy : EditRequestPolicyOperationInput; + // An operation for removing a request policy. + RemoveRequestPolicy : RemoveRequestPolicyOperationInput; + // An operation for managing system info. + ManageSystemInfo : ManageSystemInfoOperationInput; + // An operation for adding a new asset. + AddAsset : AddAssetOperationInput; + // An operation for editing an existing asset. + EditAsset : EditAssetOperationInput; + // An operation for removing an existing asset. + RemoveAsset : RemoveAssetOperationInput; + // An operation for adding a new named rule. + AddNamedRule : AddNamedRuleOperationInput; + // An operation for editing an existing named rule. + EditNamedRule : EditNamedRuleOperationInput; + // An operation for removing an existing named rule. + RemoveNamedRule : RemoveNamedRuleOperationInput; +}; + +type RequestOperationType = variant { + // A new transfer of funds from a given account. + Transfer; + // An operation for updating information of an account. + EditAccount; + // An operation for creating a new account. + AddAccount; + // An operation for creating a new address book entry. + AddAddressBookEntry; + // An operation for updating an address book entry. + EditAddressBookEntry; + // An operation for removing an address book entry. + RemoveAddressBookEntry; + // An operation for adding a new user. + AddUser; + // An operation for editing an existing user. + EditUser; + // An operation for adding a new user group. + AddUserGroup; + // An operation for editing an existing user group. + EditUserGroup; + // An operation for removing an existing user group. + RemoveUserGroup; + // An operation for performing a system upgrade on the station or upgrader. + SystemUpgrade; + // An operation for restoring the station or upgrader from a snapshot. + SystemRestore; + // An operation for setting disaster recovery for a canister. + SetDisasterRecovery; + // An operation for changing a external canister. + ChangeExternalCanister; + // An operation for creating a external canister. + ConfigureExternalCanister; + // An operation for creating a external canister. + CreateExternalCanister; + // An operation for calling an external canister. + CallExternalCanister; + // An operation for sending cycles to an external canister. + FundExternalCanister; + // An operation for monitoring cycles of an external canister. + MonitorExternalCanister; + // An operation for snapshotting an external canister. + SnapshotExternalCanister; + // An operation for restoring an external canister from a snapshot. + RestoreExternalCanister; + // An operation for pruning an external canister. + PruneExternalCanister; + // An operation for editing an permission. + EditPermission; + // An operation for adding a request policy. + AddRequestPolicy; + // An operation for editing a request policy. + EditRequestPolicy; + // An operation for removing a request policy. + RemoveRequestPolicy; + // And operation for managing system info. + ManageSystemInfo; + // An operation for adding a new asset. + AddAsset; + // An operation for editing an existing asset. + EditAsset; + // An operation for removing an existing asset. + RemoveAsset; + // An operation for adding a new named rule. + AddNamedRule; + // An operation for editing an existing named rule. + EditNamedRule; + // An operation for removing an existing named rule. + RemoveNamedRule; +}; + +// The schedule for executing a transaction of a given transfer. +type RequestExecutionSchedule = variant { + // The transaction will be executed immediately. + Immediate; + // The transaction will be executed at a given time. + Scheduled : record { + // The time at which the transaction will be executed, + // it must be in the future. + execution_time : TimestampRFC3339; + }; +}; + +// A record type that can be used to represent the caller privileges for a given request. +type RequestCallerPrivileges = record { + // The request id. + id : UUID; + // Whether or not the caller can submit an approval decision. + can_approve : bool; +}; + +// A record type that can be used to represent additional information about a request. +type RequestAdditionalInfo = record { + // The request id. + id : UUID; + // The requester name (e.g. "John Doe"). + requester_name : text; + // Display information for the approvers. + approvers : vec DisplayUser; + // The evaluation result of all matching policies for the request. + evaluation_result : opt RequestEvaluationResult; +}; + +// A record type that can be used to represent a requested operation in the station. +type Request = record { + // The request id which is a UUID (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + id : UUID; + // The request title. + title : text; + // The request summary (e.g. "This request will transfer 100 ICP to the account 0x1234"). + summary : opt text; + // The operation that was requested. + operation : RequestOperation; + // The user that created the request. + requested_by : UUID; + // The list of user approvals on the request. + approvals : vec RequestApproval; + // The time at which the request was created. + created_at : TimestampRFC3339; + // The request approval status. + status : RequestStatus; + // The time at which the request will expire. + expiration_dt : TimestampRFC3339; + // The time at which the request should be executed if approved. + execution_plan : RequestExecutionSchedule; + // The optional deduplication key used to ensure request uniqueness. + deduplication_key : opt text; + // The tags that were provided during request creation. + tags : vec text; +}; + +// The input type for creating a request. +type CreateRequestInput = record { + // The operation that was requested. + operation : RequestOperationInput; + // The request title (e.g. "Payment to John"). + title : opt text; + // The request summary (e.g. "This request will transfer 100 ICP to the account 0x1234"). + summary : opt text; + // The time at which the request will execute if approved. + execution_plan : opt RequestExecutionSchedule; + // The time at which the request will expire if still pending. + expiration_dt : opt TimestampRFC3339; + // The optional deduplication key used to ensure request uniqueness. + deduplication_key : opt text; + // The list of tags for the request. + tags : opt vec text; +}; + +// The result type for creating a request. +type CreateRequestResult = variant { + Ok : record { + // The request that was created. + request : Request; + // The privileges of the caller. + privileges : RequestCallerPrivileges; + // The additional info about the request. + additional_info : RequestAdditionalInfo; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// The input type for canceling a request. +type CancelRequestInput = record { + // The request id to cancel. + request_id : UUID; + // The reason for canceling the request. + reason : opt text; +}; + +// The result type for canceling a request. +type CancelRequestResult = variant { + Ok : record { + // The request that was canceled. + request : Request; + }; + Err : Error; +}; + +type ListRequestsOperationType = variant { + // A new transfer of funds from a given account. + Transfer : opt UUID; + // An operation for updating information of an account. + EditAccount; + // An operation for creating a new account. + AddAccount; + // An operation for adding a new user. + AddUser; + // An operation for editing an existing user. + EditUser; + // An operation for creating a new address book entry. + AddAddressBookEntry; + // An operation for updating an address book entry. + EditAddressBookEntry; + // An operation for removing an address book entry. + RemoveAddressBookEntry; + // An operation for adding a new user group. + AddUserGroup; + // An operation for editing an existing user group. + EditUserGroup; + // An operation for removing an existing user group. + RemoveUserGroup; + // An operation for performing a system upgrade on the station or upgrader. + SystemUpgrade; + // An operation for restoring the station or upgrader from a snapshot. + SystemRestore; + // An operation for changing a external canister with an optionally specified canister ID. + ChangeExternalCanister : opt principal; + // An operation for configuring an external canister. + ConfigureExternalCanister : opt principal; + // An operation for creating a external canister. + CreateExternalCanister; + // An operation for calling an external canister with an optionally specified canister ID. + CallExternalCanister : opt principal; + // An operation for sending cycles to an external canister. + FundExternalCanister : opt principal; + // An operation for monitoring cycles of an external canister. + MonitorExternalCanister : opt principal; + // An operation for snapshotting an external canister. + SnapshotExternalCanister : opt principal; + // An operation for restoring an external canister from a snapshot. + RestoreExternalCanister : opt principal; + // An operation for pruning an external canister. + PruneExternalCanister : opt principal; + // An operation for editing an permission. + EditPermission; + // An operation for adding a request policy. + AddRequestPolicy; + // An operation for editing a request policy. + EditRequestPolicy; + // An operation for removing a request policy. + RemoveRequestPolicy; + // An operation for managing system info. + ManageSystemInfo; + // An operation for setting disaster recovery config. + SetDisasterRecovery; + // An operation for adding an asset. + AddAsset; + // An operation for editing an asset. + EditAsset; + // An operation for removing an asset. + RemoveAsset; + // An operation for adding a named rule. + AddNamedRule; + // An operation for editing a named rule. + EditNamedRule; + // An operation for removing a named rule. + RemoveNamedRule; +}; + +// The direction to use for sorting. +type SortByDirection = variant { + // Sort in ascending order. + Asc; + // Sort in descending order. + Desc; +}; + +// The input type that can be used to sort the list of requests by a given field. +type ListRequestsSortBy = variant { + // Sort by the request creation time. + CreatedAt : SortByDirection; + // Sort by the request expiration time. + ExpirationDt : SortByDirection; + // Sort by the request last modification time. + LastModificationDt : SortByDirection; +}; + +// The input type for getting the list of requests based on the given filters. +type ListRequestsInput = record { + // Show only requests made by the given users. + requester_ids : opt vec UUID; + // Show only requests that the specified users have submitted an approval decision for. + approver_ids : opt vec UUID; + // Show only requests with the given status. + statuses : opt vec RequestStatusCode; + // The type of the request (e.g. "transfer"). + operation_types : opt vec ListRequestsOperationType; + // From which expiration time to retrieve the requests. + expiration_from_dt : opt TimestampRFC3339; + // Until which expiration time to retrieve the requests. + expiration_to_dt : opt TimestampRFC3339; + // From which created time to retrieve the requests. + created_from_dt : opt TimestampRFC3339; + // Until which created time to retrieve the requests. + created_to_dt : opt TimestampRFC3339; + // The pagination parameters. + paginate : opt PaginationInput; + // The sorting parameters. + sort_by : opt ListRequestsSortBy; + // Return only requests the the user can submit an approval decision for. + only_approvable : bool; + // Return the full evaluation results for the requests. + with_evaluation_results : bool; + // Return only requests with one of these deduplication keys. + deduplication_keys : opt vec text; + // The tags to search. Return only requests which have at least one matching tag. + tags : opt vec text; +}; + +// The result type for getting the list of requests. +type ListRequestsResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of requests. + requests : vec Request; + // The total number of requests. + total : nat64; + // The next offset to use for pagination. + next_offset : opt nat64; + // The privileges of the caller. + privileges : vec RequestCallerPrivileges; + // The additional info about the requests. + additional_info : vec RequestAdditionalInfo; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// Input type for getting a request. +type GetRequestInput = record { + // The request id to retrieve. + request_id : UUID; + // Fill in all the additional info about the request operation, request types such as `CallExternalCanisterOperation` + // will include the request argument, this can be a large amount of data and could potentially exceed the response + // size limit. + // + // If not provided, this field defaults to `false` and the additional info is not included in the response. + with_full_info : opt bool; +}; + +type GetRequestResultData = record { + // The request that was requested. + request : Request; + // The privileges of the caller. + privileges : RequestCallerPrivileges; + // The additional info about the request. + additional_info : RequestAdditionalInfo; +}; + +// Result type for retrieving a request. +type GetRequestResult = variant { + // The result data for a successful execution. + Ok : GetRequestResultData; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// The input type for getting the list of requests based on the given filters. +type GetNextApprovableRequestInput = record { + // The type of the request (e.g. "transfer"). + operation_types : opt vec ListRequestsOperationType; + // Exclude requests the user indicated to skip. + excluded_request_ids : vec UUID; + // Get the next request from a list sorted by the given field. + sort_by : opt ListRequestsSortBy; +}; + +// Result type for retrieving a request. +type GetNextApprovableRequestResult = variant { + // The result data for a successful execution. + Ok : opt GetRequestResultData; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// Input type for submitting an approval decision on a request. +type SubmitRequestApprovalInput = record { + // The request id to interact with. + request_id : UUID; + // The decision to submit. + decision : RequestApprovalStatus; + // The reason for the approval or rejection. + reason : opt text; +}; + +// Result type for submitting an approval decision on a request. +type SubmitRequestApprovalResult = variant { + Ok : record { + // The request that the decision was submitted for. + request : Request; + // The privileges of the caller. + privileges : RequestCallerPrivileges; + // The additional info about the request. + additional_info : RequestAdditionalInfo; + }; + Err : Error; +}; + +// A record type that can be used to represent a account balance. +type AccountBalanceInfo = record { + // Balance of the account. + balance : nat; + // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.). + decimals : nat32; + // The time at which the balance was last updated. + last_update_timestamp : TimestampRFC3339; +}; + +// A record type that can be used search for accounts. +type ListAccountsInput = record { + // The name of the account to search for. + search_term : opt text; + // The pagination parameters. + paginate : opt PaginationInput; +}; + +// Result type for listing all accounts. +type ListAccountsResult = variant { + Ok : record { + // The list of accounts. + accounts : vec Account; + // The offset to use for the next page. + next_offset : opt nat64; + // The total number of users. + total : nat64; + // The privileges of the caller. + privileges : vec AccountCallerPrivileges; + }; + Err : Error; +}; + +type ListAccountTransfersInput = record { + // The account id to retrieve. + account_id : UUID; + // The transfer status in text format (e.g. "pending", "approved", etc.). + status : opt TransferStatusType; + // From which date to retrieve the transfers. + from_dt : opt TimestampRFC3339; + // Until which date to retrieve the transfers. + to_dt : opt TimestampRFC3339; +}; + +type TransferListItem = record { + // The transfer id. + transfer_id : UUID; + // The id of the request that created the transfer. + request_id : UUID; + // The destination address of the transaction (e.g. "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"). + to : text; + // The amount to transfer. + amount : nat; + // The status of the transfer. + status : TransferStatus; + // The time at which the transfer was created. + created_at : TimestampRFC3339; +}; + +type ListAccountTransfersResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of transfers. + transfers : vec TransferListItem; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// A record type that can be used to represent the privileges of a caller for a given user group. +type UserGroupCallerPrivileges = record { + // The user id. + id : UUID; + // Whether or not the caller can edit the user group. + can_edit : bool; + // Whether or not the caller can delete the user group. + can_delete : bool; +}; + +// A record type that can be used to represent a user group in the station. +type UserGroup = record { + // The UUID of the group (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + id : UUID; + // The name of the group (e.g. "Finance"). + name : text; +}; + +type UserStatus = variant { + // The user is active. + Active; + // The user is inactive. + Inactive; +}; + +// A record type that can be used to represent the privileges of a caller for a given user. +type UserCallerPrivileges = record { + // The user id. + id : UUID; + // Whether or not the caller can edit the user. + can_edit : bool; +}; + +// A record type that can be used to represent a user in the station. +type User = record { + // The UUID of the user (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + id : UUID; + // The user name (e.g. "John Doe"). + name : text; + // The status of the user (e.g. `Active`). + status : UserStatus; + // The list of groups the user belongs to. + // + // Users can be tagged with groups that can be used to control access to resources. + groups : vec UserGroup; + // The principals associated with the user. + identities : vec principal; + // The time at which the user was created or last modified (e.g. "2021-01-01T00:00:00Z"). + last_modification_timestamp : TimestampRFC3339; +}; + +// The blockchain network to used in a transaction. +type Network = record { + // The network id, represented by the blockchain symbol and network name (e.g. "icp:mainnet"). + id : NetworkId; + // The name of the network (e.g. "Mainnet"). + name : text; +}; + +// Transfers can have additional information attached to them, +// this type can be used to represent the additional info. +type TransferMetadata = record { + // The key of the additional info (e.g. "nonce", "tag", "memo", etc...) + key : text; + // The value of the additional info (e.g. "0x1234" or "my-tag") + value : text; +}; + +// The status of a transfer. +type TransferStatus = variant { + // The transfer is created for processing. + Created; + // The transfer has been failed. + Failed : record { + // The failure reason. + reason : text; + }; + // The transfer is being processed. + Processing : record { + // The time at which the transfer started being processed. + started_at : TimestampRFC3339; + }; + // The transfer has been completed. + // + // For natively supported tokens this means that the transaction has + // submitted to the blockchain. For non natively supported tokens this means + // that the transaction has been signed and can be submitted by the client. + Completed : record { + // Time at which the transaction was completed. + completed_at : TimestampRFC3339; + // The transaction hash, if available. + hash : opt text; + // The base64 encoded value of the signed transaction, if available. + signature : opt text; + }; +}; + +// Transfer status type for filtering on the transfer status. +type TransferStatusType = variant { + Created; + Failed; + Processing; + Completed; +}; + +// A record type that can be used to represent a transfer in a given account. +type Transfer = record { + // The internal transfer id, this a unique identifier for the transfer. + id : UUID; + // The id of the request that created the transfer. + request_id : UUID; + // The account id to use for the transfer. + from_account_id : UUID; + // The destination address of the transaction (e.g. "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"). + to : text; + // The fee to pay for the transaction, if applicable. + fee : nat; + // The amount to transfer. + amount : nat; + // The status of the transfer. + status : TransferStatus; + // The network used when submitting the transaction to the blockchain. + network : Network; + // Transfers can be tagged with optional additional info (e.g. a `nonce` for Ethereum transactions). + metadata : vec TransferMetadata; +}; + +type GetTransfersInput = record { + // The list of transfer ids to retrieve. + transfer_ids : vec UUID; +}; + +type GetTransfersResult = variant { + // The result data for a successful execution. + Ok : record { + // The transfer that was retrieved. + transfers : vec Transfer; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// Generic error type added to responses that can fail. +type Error = record { + // Error code, added as a string to allow for custom error codes. + code : text; + // Error message to be displayed to the user. + message : opt text; + // Error details to be displayed to the user. + details : opt vec record { text; text }; +}; + +// Account can have additional information attached to them, +// this type can be used to represent the additional info. +type AccountMetadata = record { + // The key of the additional info (e.g. "contract") + key : text; + // The value of the additional info (e.g. "0x1234") + value : text; +}; + +// A record type that can be used to represent the privileges of a caller for a given account. +type AccountCallerPrivileges = record { + // The account id that the caller has privileges for. + id : UUID; + // Whether or not the caller can edit the account. + can_edit : bool; + // Whether or not the caller can request transfers from the account. + can_transfer : bool; +}; + +// A record type that can be used to represent a account in the canister. +type Account = record { + // The internal account id. + id : UUID; + + // The list of assets supported by this account. + assets : vec AccountAsset; + + // The list of addresses associated with the account. + addresses : vec AccountAddress; + + // A friendly name for the account. + name : text; + // Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`). + metadata : vec AccountMetadata; + // The transfer approval policy for the account. + // + // The transfer approval policy defines the rule that must be met for a transfer to be approved. + transfer_request_policy : opt RequestPolicyRule; + // The configs approval policy for the account. + // + // The configs approval policy defines the rule that must be met for the account to have its configs updated. + configs_request_policy : opt RequestPolicyRule; + // The time at which the account was created or last modified (e.g. "2021-01-01T00:00:00Z"). + last_modification_timestamp : TimestampRFC3339; +}; + +// The seed used to derive the addresses of the account. +type AccountSeed = blob; + +// Record type to describe an address of an account. +type AccountAddress = record { + // The address. + address : text; + // The format of the address, eg. icp_account_identifier. + format : text; +}; + +// Record type to describe an asset of an account. +type AccountAsset = record { + // The asset id. + asset_id : UUID; + // The balance of the asset. + balance : opt AccountBalance; +}; + +// Input type for getting a account. +type GetAccountInput = record { + // The account id to retrieve. + account_id : UUID; +}; + +// Result type for getting a account. +type GetAccountResult = variant { + // The result data for a successful execution. + Ok : record { + // The account that was retrieved. + account : Account; + // The privileges of the caller for the account. + privileges : AccountCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +type AccountBalance = record { + // The account id. + account_id : UUID; + // The asset id. + asset_id : UUID; + // The balance of the account. + balance : nat; + // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.). + decimals : nat32; + // The time at which the balance was last updated. + last_update_timestamp : TimestampRFC3339; + // The state of balance query: + // - `fresh`: The balance was recently updated and is considered fresh. + // - `stale`: The balance may be out of date. + // - `stale_refreshing`: The balance may be out of date but it is being refreshed in the background. + query_state : text; +}; + +// Input type for getting a account balance. +type FetchAccountBalancesInput = record { + // The account ids to retrieve. + account_ids : vec UUID; +}; + +// Result type for getting a account. +type FetchAccountBalancesResult = variant { + // The result data for a successful execution. + Ok : record { + // The account balance that was retrieved. + balances : vec opt AccountBalance; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// Address book entries can have additional information attached to them, +// this type can be used to represent the additional info. +type AddressBookMetadata = record { + // The key of the additional info (e.g. "kyc") + key : text; + // The value of the additional info (e.g. "true") + value : text; +}; + +// A record type that can be used to represent the privileges of a caller for a given address book entry. +type AddressBookEntryCallerPrivileges = record { + // The address book entry id. + id : UUID; + // Whether or not the caller can edit the address book entry. + can_edit : bool; + // Whether or not the caller can delete the address book entry. + can_delete : bool; +}; + +// A record type that can be used to represent an address book entry in the canister. +type AddressBookEntry = record { + // The internal address book entry id. + id : UUID; + // The address owner. + address_owner : text; + // The actual address. + address : text; + // The address format (e.g. "icp_account_identifier"). + address_format : text; + // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + blockchain : text; + // Metadata associated with the address book entry (e.g. `{"kyc": "true"}`). + metadata : vec AddressBookMetadata; + // The list of labels associated with the address book entry (e.g. `["kyc", "approved"]`). + labels : vec text; + // The time at which the address book entry was created or last modified (e.g. "2021-01-01T00:00:00Z"). + last_modification_timestamp : text; +}; + +// Input type for getting a single address book entry. +type GetAddressBookEntryInput = record { + // The address book entry id to retrieve. + address_book_entry_id : UUID; +}; + +// Result type for getting an address book entry. +type GetAddressBookEntryResult = variant { + // The result data for a successful execution. + Ok : record { + // The address book entry that was retrieved. + address_book_entry : AddressBookEntry; + // The privileges of the caller for the address book entry. + privileges : AddressBookEntryCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// Input type for listing address book entries for a given blockchain standard. +type ListAddressBookEntriesInput = record { + // The address boo entry ids to retrieve. + ids : opt vec UUID; + // The address to search for. + addresses : opt vec text; + // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + blockchain : opt text; + // The labels to search for, if provided only address book entries with the given labels will be returned. + labels : opt vec text; + // The address formats to search for. + address_formats : opt vec text; + // The term to use for filtering the address book entries. + search_term : opt text; + // The pagination parameters. + paginate : opt PaginationInput; +}; + +// Result type for listing address book entries for a given blockchain standard. +type ListAddressBookEntriesResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of retrieved address book entries. + address_book_entries : vec AddressBookEntry; + // The offset to use for the next page. + next_offset : opt nat64; + // The total number of address book entries for the given blockchain standard. + total : nat64; + // The privileges of the caller for the address book entries. + privileges : vec AddressBookEntryCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// Assets can have additional information attached to them, +// this type can be used to represent the additional info. +type AssetMetadata = record { + // The key of the additional info (e.g. "logo") + key : text; + // The value of the additional info (e.g. "https://example.com/logo.png") + value : text; +}; + +// A record type that can be used to represent an asset in the station. +type Asset = record { + // The internal asset id. + id : UUID; + // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + blockchain : text; + // The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string + // with spaces replaced with underscores. + standards : vec text; + // The asset symbol, e.g. "ICP" or "BTC". + symbol : AssetSymbol; + // The asset name (e.g. `Internet Computer`, `Bitcoin`, `Ethereum`, etc.) + name : text; + // The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`). + metadata : vec AssetMetadata; + // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.). + decimals : nat32; +}; + +// Describes a standard suported by a blockchain. +type StandardData = record { + // The standard name. + standard : text; + // Required metadata fields for the standard (e.g. `["ledger_canister_id"]`). + required_metadata_fields : vec text; + // Supported operations for the standard (e.g. `["transfer", "list_transfers", "balance"]`). + supported_operations : vec text; + // Supported address formats of the standard. + supported_address_formats : vec text; +}; + +// Describes a blockchain and its standards supported by the station. +type SupportedBlockchain = record { + // The blockchain name. + blockchain : text; + // The supported standards for the blockchain. + supported_standards : vec StandardData; +}; + +// The named rule type. +// +// A named rule is a reusable configuration that can be applied to many approval policies. +type NamedRule = record { + // The rule id. + id : UUID; + // The rule name. + name : text; + // The rule description. + description : opt text; + // The rule value. + rule : RequestPolicyRule; +}; + +// A record type that can be used to represent the caller privileges for a given named rule. +type NamedRuleCallerPrivileges = record { + // The named rule id. + id : UUID; + // Whether or not the caller can edit the resource. + can_edit : bool; + // Whether or not the caller can delete the resource. + can_delete : bool; +}; + +// Input type for getting a named rule. +type GetNamedRuleInput = record { + // The named rule to retrieve by the id. + named_rule_id : UUID; +}; + +// Result type for retrieving a named rule. +type GetNamedRuleResult = variant { + // The result data for a successful execution. + Ok : record { + // The named rule that was retrieved. + named_rule : NamedRule; + // The privileges of the caller for the named rule. + privileges : NamedRuleCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// Input type for listing named rules. +type ListNamedRulesInput = record { + // The pagination parameters. + paginate : opt PaginationInput; +}; + +// Result type for listing named rules. +type ListNamedRulesResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of named rules. + named_rules : vec NamedRule; + // The offset to use for the next page. + next_offset : opt nat64; + // The total number of named rules. + total : nat64; + // The privileges of the caller. + privileges : vec NamedRuleCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// The operation type for adding a new named rule. +type AddNamedRuleOperation = record { + // The result of adding a named rule. + named_rule : opt NamedRule; + // The input to the request to add a named rule. + input : AddNamedRuleOperationInput; +}; + +// The input type for creating a named rule. +type AddNamedRuleOperationInput = record { + // The rule name. + name : text; + // The rule description. + description : opt text; + // The rule value. + rule : RequestPolicyRule; +}; + +// The operation type for editing an existing named rule. +type EditNamedRuleOperation = record { + // The input to the request to edit a named rule. + input : EditNamedRuleOperationInput; +}; + +// The input type for editing a named rule. +type EditNamedRuleOperationInput = record { + // The named rule id to edit. + named_rule_id : UUID; + // The rule name. + name : opt text; + // The optional rule description. + description : opt opt text; + // The rule value. + rule : opt RequestPolicyRule; +}; + +// The operation type for removing a named rule. +type RemoveNamedRuleOperation = record { + // The input to the request to remove a named rule. + input : RemoveNamedRuleOperationInput; +}; + +// The input type for deleting a named rule. +type RemoveNamedRuleOperationInput = record { + // The named rule id to remove. + named_rule_id : UUID; +}; + +// A record type that is used to show the current capabilities of the station. +type Capabilities = record { + // The name of the station. + name : text; + // Version of the station. + version : text; + // The list of supported assets. + supported_assets : vec Asset; + // The list of supported blockchains and standards. + supported_blockchains : vec SupportedBlockchain; +}; + +// Result type for getting the current config. +type CapabilitiesResult = variant { + // The result data for a successful execution. + Ok : record { + // The config. + capabilities : Capabilities; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// An operation for managing the system information. +type ManageSystemInfoOperation = record { + // The input to the request to manage the system information. + input : ManageSystemInfoOperationInput; +}; + +// Input type for managing the system information. +type ManageSystemInfoOperationInput = record { + // The name of the station. + name : opt text; + // The strategy to use to for the station to top itself up with cycles. + cycle_obtain_strategy : opt CycleObtainStrategyInput; + // The maximum number of station backup snapshots to keep. + max_station_backup_snapshots : opt nat64; + // The maximum number of upgrader backup snapshots to keep. + max_upgrader_backup_snapshots : opt nat64; +}; + +// Strategy defining how the station canister tops up its own cycles. +type CycleObtainStrategyInput = variant { + // Do not obtain cycles for Orbit. + Disabled; + // Use the CMC to mint cycles from ICP held in an Orbit account. + MintFromNativeToken : record { + // The Orbit account ID to use for minting cycles. + account_id : UUID; + }; + // Use the Cycles Ledger balance to obtain cycles. + WithdrawFromCyclesLedger : record { + // The Orbit account ID to use for obtaining cycles. + account_id : UUID; + }; +}; + +// Strategy defining how the station canister tops up its own cycles. +type CycleObtainStrategy = variant { + // Do not obtain cycles for Orbit. + Disabled; + // Use the CMC to mint cycles from ICP held in an Orbit account. + MintFromNativeToken : record { + // The Orbit account ID to use for minting cycles. + account_id : UUID; + // The Orbit account name. + account_name : opt text; + }; + // Use the Cycles Ledger balance to obtain cycles. + WithdrawFromCyclesLedger : record { + // The Orbit account ID to use for obtaining cycles. + account_id : UUID; + // The Orbit account name. + account_name : opt text; + }; +}; + +// The system information. +type SystemInfo = record { + // The name of the station. + name : text; + // The station version. + version : text; + // The upgrader principal id. + upgrader_id : principal; + // Cycle balance of the station. + cycles : nat64; + // Cycle balance of the canister. + upgrader_cycles : opt nat64; + // The time at which the canister was last upgraded. + last_upgrade_timestamp : TimestampRFC3339; + // Did the canister successfully fetched randomness from the management canister. + raw_rand_successful : bool; + // The disaster recovery configuration. + disaster_recovery : opt DisasterRecovery; + // Strategy defining how the station canister tops up its own cycles. + cycle_obtain_strategy : CycleObtainStrategy; + // The maximum number of station backup snapshots to keep. + max_station_backup_snapshots : nat64; + // The maximum number of upgrader backup snapshots to keep. + max_upgrader_backup_snapshots : nat64; +}; + +// The disaster recovery committee extended with the user group name. +type DisasterRecovery = record { + // The disaster recovery committee. + committee : DisasterRecoveryCommittee; + // The name of the disaster recovery committee user group. + user_group_name : opt text; +}; + +// Result type for getting the canister system information. +type SystemInfoResult = variant { + // The result data for a successful execution. + Ok : record { + // The system information. + system : SystemInfo; + }; + // The error that occurred (e.g. the caller does not have sufficient privileges). + Err : Error; +}; + +// Input type for retrieving a user. +type GetUserInput = record { + // The user id to retrieve (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + user_id : UUID; +}; + +// Result type for retrieving a user. +type GetUserResult = variant { + // The result data for a successful execution. + Ok : record { + // The user that was retrieved. + user : User; + // The caller privileges for the user. + privileges : UserCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// Input type for listing users of the station. +type ListUsersInput = record { + // The search term to use for filtering the users. + search_term : opt text; + // The statuses to use for filtering the users. + statuses : opt vec UserStatus; + // The groups to use for filtering the users. + groups : opt vec UUID; + // The pagination parameters. + paginate : opt PaginationInput; +}; + +// Result type for listing users of the station. +type ListUsersResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of users. + users : vec User; + // The offset to use for the next page. + next_offset : opt nat64; + // The total number of users. + total : nat64; + // The privileges of the caller. + privileges : vec UserCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// The record id of a resource, used to specify the resource that is being accessed. +type ResourceId = variant { + Any; + Id : UUID; +}; + +// The record ids of a resource, used to specify the resources that are being accessed. +type ResourceIds = variant { + Any; + Ids : vec UUID; +}; + +// The allow rules for who can access the resource. +type Allow = record { + // Required authentication level for accessing the resource. + auth_scope : AuthScope; + // Only the specified users can access the resource. + users : vec UUID; + // Only the specified user groups can access the resource. + user_groups : vec UUID; +}; + +// The authorization scope the caller must have, used to specify the required scope for accessing a resource. +type AuthScope = variant { + // Allows access to the resource without requiring any authentication. + Public; + // Requires to be an authenticated user to access the resource. + Authenticated; + // Requires the caller to have direct access to the resource through user groups or user ids. + Restricted; +}; + +// The resource actions, used to specify the action that is performed on a resource. +type ResourceAction = variant { + List; + Create; + Read : ResourceId; + Update : ResourceId; + Delete : ResourceId; +}; + +// The actions that are available for accounts. +type AccountResourceAction = variant { + List; + Create; + Transfer : ResourceId; + Read : ResourceId; + Update : ResourceId; +}; + +// The target canister to interact with. +type ExternalCanisterId = variant { + Any; + Canister : principal; +}; + +// The actions that are available for external canisters. +type ExternalCanisterResourceAction = variant { + List; + Create; + Change : ExternalCanisterId; + Read : ExternalCanisterId; + Fund : ExternalCanisterId; + Call : CallExternalCanisterResourceTarget; +}; + +// The validation method targets of a `CallExternalCanister` request. +type ValidationMethodResourceTarget = variant { + No; + ValidationMethod : CanisterMethod; +}; + +// The execution method targets of a `CallExternalCanister` request. +type ExecutionMethodResourceTarget = variant { + Any; + ExecutionMethod : CanisterMethod; +}; + +// The validation and execution method targets of a `CallExternalCanister` request. +type CallExternalCanisterResourceTarget = record { + validation_method : ValidationMethodResourceTarget; + execution_method : ExecutionMethodResourceTarget; +}; + +// The actions that are available for notifications. +type NotificationResourceAction = variant { + List; + Update : ResourceId; +}; + +// The actions that are available for requests. +type RequestResourceAction = variant { + List; + Read : ResourceId; +}; + +// The actions that are available for the system. +type SystemResourceAction = variant { + SystemInfo; + Capabilities; + ManageSystemInfo; + Upgrade; +}; + +// The actions that are available for users. +type UserResourceAction = variant { + List; + Create; + Read : ResourceId; + Update : ResourceId; +}; + +// The actions that are available for permissions. +type PermissionResourceAction = variant { + Read; + Update; +}; + +// The Resource is used to specify what is being accessed. +type Resource = variant { + Permission : PermissionResourceAction; + Account : AccountResourceAction; + AddressBook : ResourceAction; + ExternalCanister : ExternalCanisterResourceAction; + Notification : NotificationResourceAction; + Request : RequestResourceAction; + RequestPolicy : ResourceAction; + System : SystemResourceAction; + User : UserResourceAction; + UserGroup : ResourceAction; + Asset : ResourceAction; + NamedRule : ResourceAction; +}; + +// A record type that can be used to represent the caller privileges for a given permission. +type PermissionCallerPrivileges = record { + // The resource that the caller has privileges for. + resource : Resource; + // Whether or not the caller can edit the resource. + can_edit : bool; +}; + +// The permission, used to specify the rules for users when interacting with resources. +type Permission = record { + // The allowed users and user groups for the resource. + allow : Allow; + // The resource that the permission is for. + resource : Resource; +}; + +// Input type for listing permissions with the given pagination parameters. +type ListPermissionsInput = record { + // The resources to retrieve the permissions for. + resources : opt vec Resource; + // The pagination parameters. + paginate : opt PaginationInput; +}; + +// A basic user record that can be used to represent a user in the station. +type BasicUser = record { + // The UUID of the user (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + id : UUID; + // The user name (e.g. "John Doe"). + name : text; + // The status of the user (e.g. `Active`). + status : UserStatus; +}; + +// A minimal user record that is meant to aid displaying users on the client. +type DisplayUser = record { + // The UUID of the user (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + id : UUID; + // The user name (e.g. "John Doe"). + name : text; +}; + +// Result type for listing permissions. +type ListPermissionsResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of permissions. + permissions : vec Permission; + // The user groups that are associated with returned permissions. + user_groups : vec UserGroup; + // The users that are associated with returned permissions. + users : vec BasicUser; + // The offset to use for the next page. + next_offset : opt nat64; + // The total number of permissions. + total : nat64; + // The caller privileges for the permissions. + privileges : vec PermissionCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +type GetPermissionInput = record { + // The resource to retrieve the permission for. + resource : Resource; +}; + +type GetPermissionResult = variant { + // The result data for a successful execution. + Ok : record { + // The permission that was retrieved. + permission : Permission; + // The privileges of the caller for the permission. + privileges : PermissionCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +type ListUserGroupsInput = record { + // The term to use for filtering the user groups. + search_term : opt text; + // The pagination parameters. + paginate : opt PaginationInput; +}; + +// Result type for listing all user groups. +type ListUserGroupsResult = variant { + Ok : record { + // The list of groups. + user_groups : vec UserGroup; + // The offset to use for the next page. + next_offset : opt nat64; + // The total number of user groups. + total : nat64; + // The caller privileges for the user groups. + privileges : vec UserGroupCallerPrivileges; + }; + Err : Error; +}; + +// Input type for getting a user group. +type GetUserGroupInput = record { + // The group id to retrieve. + user_group_id : UUID; +}; + +// Result type for getting a user group. +type GetUserGroupResult = variant { + // The result data for a successful execution. + Ok : record { + // The group that was retrieved. + user_group : UserGroup; + // The caller privileges for the user group. + privileges : UserGroupCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +type GetRequestPolicyInput = record { + // The id of the request policy to retrieve. + id : UUID; +}; + +type GetRequestPolicyResult = variant { + // The result data for a successful execution. + Ok : record { + // The request policy that was retrieved. + policy : RequestPolicy; + // The privileges of the caller for the request policy. + privileges : RequestPolicyCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// Input type for listing request policies with the given pagination parameters. +type ListRequestPoliciesInput = PaginationInput; + +// Result type for listing request policies. +type ListRequestPoliciesResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of request policies. + policies : vec RequestPolicy; + // The offset to use for the next page. + next_offset : opt nat64; + // The total number of request policies. + total : nat64; + // The caller privileges for the request policies. + privileges : vec RequestPolicyCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +type AddAssetOperation = record { + // The result of adding an asset. + asset : opt Asset; + // The input to the request to add an asset. + input : AddAssetOperationInput; +}; + +// The input type for adding an asset. +type AddAssetOperationInput = record { + // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + blockchain : text; + // The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string + // with spaces replaced with underscores. + standards : vec text; + // The asset symbol, e.g. "ICP" or "BTC". + symbol : AssetSymbol; + // The asset name (e.g. `Internet Computer`, `Bitcoin`, `Ethereum`, etc.) + name : text; + // The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`). + metadata : vec AssetMetadata; + // The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.). + decimals : nat32; +}; + +type EditAssetOperation = record { + // The input to the request to edit an asset. + input : EditAssetOperationInput; +}; + +// The input type for editing an asset. +type EditAssetOperationInput = record { + // The asset id to edit. + asset_id : UUID; + // The name of the asset. + name : opt text; + // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + blockchain : opt text; + // The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string + // with spaces replaced with underscores. + standards : opt vec text; + // The asset symbol, e.g. "ICP" or "BTC". + symbol : opt AssetSymbol; + // The metadata to change. + change_metadata : opt ChangeMetadata; +}; + +// Type for instructions to update the address book entry's metadata. +type ChangeMetadata = variant { + // Replace all existing metadata by the specified metadata. + ReplaceAllBy : vec AssetMetadata; + // Override values of existing metadata with the specified keys + // and add new metadata if no metadata can be found with the specified keys. + OverrideSpecifiedBy : vec AssetMetadata; + // Remove metadata with the specified keys. + RemoveKeys : vec text; +}; + +type RemoveAssetOperation = record { + // The input to the request to remove an asset. + input : RemoveAssetOperationInput; +}; + +// The input type for removing an asset. +type RemoveAssetOperationInput = record { + // The asset id to remove. + asset_id : UUID; +}; + +// The input type for listing assets. +type ListAssetsInput = record { + // The pagination parameters. + paginate : opt PaginationInput; +}; + +// The result type for listing assets. +type ListAssetsResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of assets. + assets : vec Asset; + // The offset to use for the next page. + next_offset : opt nat64; + // The total number of assets. + total : nat64; + // The caller privileges for the assets. + privileges : vec AssetCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// The input type for getting an asset. +type GetAssetInput = record { + // The asset id to retrieve. + asset_id : UUID; +}; + +// The result type for getting an asset. +type GetAssetResult = variant { + // The result data for a successful execution. + Ok : record { + // The asset that was retrieved. + asset : Asset; + // The caller privileges for the asset. + privileges : AssetCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +type AssetCallerPrivileges = record { + id : UUID; + can_edit : bool; + can_delete : bool; +}; + +// The top level privileges that the user has when making calls to the canister. +type UserPrivilege = variant { + Capabilities; + SystemInfo; + ManageSystemInfo; + ListAccounts; + AddAccount; + ListUsers; + AddUser; + ListUserGroups; + AddUserGroup; + ListPermissions; + ListRequestPolicies; + AddRequestPolicy; + ListAddressBookEntries; + AddAddressBookEntry; + SystemUpgrade; + ListRequests; + CreateExternalCanister; + ListExternalCanisters; + CallAnyExternalCanister; + ListAssets; + AddAsset; + ListNamedRules; + AddNamedRule; +}; + +type MeResult = variant { + Ok : record { + // The user that is associated with the caller. + me : User; + // The list of privileges associated with the user. + privileges : vec UserPrivilege; + }; + Err : Error; +}; + +// An input type for configuring the upgrader canister. +type SystemUpgraderInput = variant { + // An existing upgrader canister. + Id : principal; + // Creates and deploys a new canister. + Deploy : record { + wasm_module : blob; + // The initial cycles to allocate to the canister. + // + // If not set, only the minimal amount of cycles required to create + // and deploy the canister will be allocated. + initial_cycles : opt nat; + }; +}; + +// The initial accounts to create when initializing the canister for the first time, e.g., after disaster recovery. +type InitAccountInput = record { + // The UUID of the account, if not provided a new UUID will be generated. + id : opt UUID; + // A friendly name for the account (e.g. "My Account"). + name : text; + // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + seed : AccountSeed; + // The asset standard for this account (e.g. `native`, `erc20`, etc.). + assets : vec UUID; + // Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`). + metadata : vec AccountMetadata; +}; + +// The permissions for the account. +type InitAccountPermissionsInput = record { + // Who can read the account information. + read_permission : Allow; + // Who can request updates to the account. + configs_permission : Allow; + // Who can request transfers from the account. + transfer_permission : Allow; + // The approval policy for updates to the account. + configs_request_policy : opt RequestPolicyRule; + // The approval policy for transfers from the account. + transfer_request_policy : opt RequestPolicyRule; +}; + +// The initial account to create when initializing the canister for the first time. +type InitAccountWithPermissionsInput = record { + // The initial account to create. + account_init : InitAccountInput; + // The permissions for the account. + permissions : InitAccountPermissionsInput; +}; + +// The initial assets to create when initializing the canister for the first time, e.g., after disaster recovery. +type InitAssetInput = record { + // The UUID of the asset, if not provided a new UUID will be generated. + id : opt UUID; + // The name of the asset. + name : text; + // The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + blockchain : text; + // The standards this asset supports. + standards : vec text; + // The asset symbol, e.g. "ICP" or "BTC". + symbol : text; + // The number of decimals used to format the asset balance. + decimals : nat32; + // Metadata associated with the asset. + metadata : vec AssetMetadata; +}; + +// The input type for creating a user group when initializing the canister for the first time. +type InitUserGroupInput = record { + // The id of the user group, if not provided a new UUID will be generated. + id : opt UUID; + // The name of the user group, must be unique. + name : text; +}; + +// The input type for adding identities to a user. +type UserIdentityInput = record { + // The identity of the user. + identity : principal; +}; + +// The users to create when initializing the canister for the first time. +type InitUserInput = record { + // The id of the user, if not provided a new UUID will be generated. + id : opt UUID; + // The name of the user. + name : text; + // The identities of the user. + identities : vec UserIdentityInput; + // The user groups to associate with the user (optional). + // If not provided it defaults to the [`Admin`,`Operator`] groups if default user groups are created, + // i.e., when the field `initial_config` in `SystemInit` has the form of `WithAllDefaults` or `WithDefaultPolicies`. + groups : opt vec UUID; + // The status of the user (e.g. `Active`). + status : UserStatus; +}; + +// The init type for initializing the permissions when first creating the canister. +type InitPermissionInput = record { + // The resource that the permission is for. + resource : Resource; + // The allow rules for who can access the resource. + allow : Allow; +}; + +// The init type for adding a request approval policy when initializing the canister for the first time. +type InitRequestPolicyInput = record { + // The id of the request policy, if not provided a new UUID will be generated. + id : opt UUID; + // The request specifier that identifies what operation this policy is for (e.g. "transfer"). + specifier : RequestSpecifier; + // The rule to use for the request approval evaluation (e.g. "quorum"). + rule : RequestPolicyRule; +}; + +// The init type for adding a named rule when initializing the canister for the first time. +type InitNamedRuleInput = record { + // The id of the named rule, if not provided a new UUID will be generated. + id : opt UUID; + // The name of the named rule. + name : text; + // The description of the named rule. + description : opt text; + // The rule to use for the named rule. + rule : RequestPolicyRule; +}; + +// The initial configuration for the station. +// +// Unless the `Complete` variant is used, the station will be initialized with default user +// groups, named rules (aka. approval rules), request policies, permissions, and assets. +// +// The default user groups for the station will be: +// - `Admin` with the UUID "00000000-0000-4000-8000-000000000000" +// - `Operator` with the UUID "00000000-0000-4000-8000-000000000001" +// +// The default named rules for the station will be: +// - `Admin approval` with a specified admin quorum +// - `Operator approval` with a specified operator and admin quorum +// +type InitialConfig = variant { + // Initialize the station with default user groups, named rules, policies, permissions, and assets. + // This does not create an initial account. + WithAllDefaults : record { + // The initial users to create. + users : vec InitUserInput; + // The initial admin quorum in the admin level approval rule. + admin_quorum : nat16; + // The initial operator quorum in the operator level approval rule. + operator_quorum : nat16; + }; + // Initialize the station with default user groups, named rules, policies, permissions. + WithDefaultPolicies : record { + // The initial users to create. + users : vec InitUserInput; + // The initial accounts to create. + accounts : vec InitAccountInput; + // The initial assets to create. + assets : vec InitAssetInput; + // The initial admin quorum in the admin level approval rule. + admin_quorum : nat16; + // The initial operator quorum in the operator level approval rule. + operator_quorum : nat16; + }; + // Initialize the station with all custom entries. + Complete : record { + // The initial users to create. + users : vec InitUserInput; + // The initial user groups to create. + user_groups : vec InitUserGroupInput; + // The initial permissions to create. + permissions : vec InitPermissionInput; + // The initial request policies to create. + request_policies : vec InitRequestPolicyInput; + // The initial named rules to create. + named_rules : vec InitNamedRuleInput; + // The initial accounts to create. + accounts : vec InitAccountWithPermissionsInput; + // The initial assets to create. + assets : vec InitAssetInput; + // The initial disaster recovery committee to create. + disaster_recovery_committee : opt DisasterRecoveryCommittee; + }; +}; + +type SystemInit = record { + // The name of the station. + name : text; + // The upgrader configuration. + upgrader : SystemUpgraderInput; + // An additional controller of the station and upgrader canisters (optional). + fallback_controller : opt principal; + // The initial configuration to apply. + initial_config: InitialConfig; +}; + +// The upgrade configuration for the canister. +type SystemUpgrade = record { + // The updated name of the station. + name : opt text; +}; + +// The input type for the canister install method (e.g. init or upgrade). +type SystemInstall = variant { + // The configuration to use when initializing the canister. + Init : SystemInit; + // The configuration to use when upgrading the canister. + Upgrade : SystemUpgrade; +}; + +type HealthStatus = variant { + Healthy; + Uninitialized; +}; + +type CanisterStatusInput = record { + canister_id : principal; +}; + +type LogVisibility = variant { + public; + controllers; + allowed_viewers: vec principal; +}; + +type DefiniteCanisterSettings = record { + controllers : vec principal; + compute_allocation : nat; + memory_allocation : nat; + freezing_threshold : nat; + reserved_cycles_limit : nat; + log_visibility : LogVisibility; + wasm_memory_limit : nat; +}; + +type DefiniteCanisterSettingsInput = record { + controllers : opt vec principal; + compute_allocation : opt nat; + memory_allocation : opt nat; + freezing_threshold : opt nat; + reserved_cycles_limit : opt nat; + log_visibility : opt LogVisibility; + wasm_memory_limit : opt nat; +}; + +type CanisterStatusResponse = record { + status : variant { running; stopping; stopped }; + settings : DefiniteCanisterSettings; + module_hash : opt blob; + memory_size : nat; + cycles : nat; + reserved_cycles : nat; + idle_cycles_burned_per_day : nat; + query_stats : record { + num_calls_total : nat; + num_instructions_total : nat; + request_payload_bytes_total : nat; + response_payload_bytes_total : nat; + }; +}; + +// The permission for making calls to a specific or any external canister method. +type ExternalCanisterCallPermission = record { + // Allowed users and user groups for the operation. + allow : Allow; + // The validation method that is used to validate the request and + // render the argument. + validation_method : ValidationMethodResourceTarget; + // The execution method that the caller can use, + // if `*` is used the caller can use any method. + execution_method : text; +}; + +// The request policy rule for the canister call operation. +type ExternalCanisterCallRequestPolicyRule = record { + // The id of the request policy rule. + policy_id : UUID; + // The request policy rule for the canister call operation. + rule : RequestPolicyRule; + // The validation method that is used to match the policy against + // the permission of the resource. + validation_method : ValidationMethodResourceTarget; + // The method name that the rule is for, + // if `*` is used the rule applies to all methods. + execution_method : text; +}; + +// The request policy rule for the canister call operation. +type ExternalCanisterCallRequestPolicyRuleInput = record { + // The id of the request policy rule. + // + // If not provided a new entry will be created. + policy_id : opt UUID; + // The request policy rule for the canister call operation. + rule : RequestPolicyRule; + // The validation method that is used to match the policy against + // the permission of the resource. + validation_method : ValidationMethodResourceTarget; + // The method name that the rule is for, + // if `*` is used the rule applies to all methods. + execution_method : text; +}; + +// The request policy rule for the canister change operation. +type ExternalCanisterChangeRequestPolicyRule = record { + // The id of the request policy rule. + policy_id : UUID; + // The request policy rule for the canister change operation. + rule : RequestPolicyRule; +}; + +// The request policy rule for the canister change operation. +type ExternalCanisterChangeRequestPolicyRuleInput = record { + // The id of the request policy rule. + // + // If not provided a new entry will be created. + policy_id : opt UUID; + // The request policy rule for the canister change operation. + rule : RequestPolicyRule; +}; + +// The permissions set for the external canister. +type ExternalCanisterPermissions = record { + // Who can read information about the canister (e.g. canister status), + // changes to this permission can be made by the `change` permission. + read : Allow; + // Who can make changes to the canister, includes: + // - changing the permissions + // - install operations + change : Allow; + // The permissions for the calling methods on the canister. + calls : vec ExternalCanisterCallPermission; +}; + +// The create input type for setting the permissions for the external canister. +type ExternalCanisterPermissionsCreateInput = ExternalCanisterPermissions; + +// The pair that is used to represent the execution and validation method. +type CanisterExecutionAndValidationMethodPair = record { + // The validation method that is used to validate the request and + // render the argument. + validation_method : ValidationMethodResourceTarget; + // The method that the caller can call on the external canister. + // + // The `*` method name is used to represent that the caller can call any method on the canister. + execution_method : text; +}; + +// The input type for setting call permissions of an existing external canister. +type ExternalCanisterChangeCallPermissionsInput = variant { + // Replaces all the call permissions with the provided list, if the list is empty + // all the call permissions will be removed. + ReplaceAllBy : vec ExternalCanisterCallPermission; + // Override the call permissions from the specified execution methods. + OverrideSpecifiedByExecutionMethods : vec record { + // The method that the caller can call on the external canister. + // + // The `*` method name is used to represent that the caller can call any method on the canister. + execution_method : text; + // The permissions associated with the execution method, if the list is empty all call permissions of the + // execution method will be removed. + permissions : vec record { + // Allowed users and user groups for the operation. + allow : Allow; + // The validation method that is used to validate the request and render the argument. + validation_method : ValidationMethodResourceTarget; + }; + }; + // Override the permissions for the specified execution and validation method pairs. + OverrideSpecifiedByExecutionValidationMethodPairs : vec record { + // The method configuration that is used to represent the execution and validation method pair. + method_configuration : CanisterExecutionAndValidationMethodPair; + // If allow is not provided the call permission will be removed for the specified execution + // and validation method pair. + allow : opt Allow; + }; +}; + +// The input type for setting the permissions for the external canister. +type ExternalCanisterPermissionsUpdateInput = record { + // Who can read information about the canister (e.g. canister status), + // changes to this permission can be made by the `change` permission. + read : opt Allow; + // Who can make changes to the canister, includes: + // - changing the permissions + // - install operations + change : opt Allow; + // The permissions for calling methods on the canister. + calls : opt ExternalCanisterChangeCallPermissionsInput; +}; + +// The request policy rules for the external canister. +type ExternalCanisterRequestPolicies = record { + // The request policy rules for the canister change operation. + change : vec ExternalCanisterChangeRequestPolicyRule; + // The request policy rules for the calling methods on the canister. + calls : vec ExternalCanisterCallRequestPolicyRule; +}; + +// The input type for setting the request policies for a new external canister. +type ExternalCanisterRequestPoliciesCreateInput = record { + // The request policy rules for the canister change operation. + change : vec ExternalCanisterChangeRequestPolicyRuleInput; + // The request policy rules for the calling methods on the canister. + calls : vec ExternalCanisterCallRequestPolicyRuleInput; +}; + +type ExternalCanisterChangeCallRequestPoliciesInput = variant { + // Replaces all the call request policies with the provided list. + ReplaceAllBy : vec ExternalCanisterCallRequestPolicyRuleInput; + // Remove call request policies by the provided ids. + RemoveByPolicyIds : vec UUID; + // Override the request policies for the specified execution methods. + OverrideSpecifiedByExecutionMethods : vec record { + // The method that the caller can call on the external canister. + // + // The `*` method name is used to represent that the caller can call any method on the canister. + execution_method : text; + // The request policies associated with the execution method, if the list is empty all the policies of + // the execution method will be removed. + policies : vec record { + // The id of the request policy rule. + // + // If not provided a new entry will be created. + policy_id : opt UUID; + // The request policy rule for the canister call operation. + rule : RequestPolicyRule; + // The validation method that is used to match the policy against + // the permission of the resource. + validation_method : ValidationMethodResourceTarget; + }; + }; + // Override the request policies for the specified execution and validation method pairs. + OverrideSpecifiedByExecutionValidationMethodPairs : vec record { + // The method configuration that is used to represent the execution and validation method pair. + method_configuration : CanisterExecutionAndValidationMethodPair; + // The request policies to use for the method configuration, if the list is empty all the policies of + // the execution and validation method pair will be removed. + policies : vec ExternalCanisterChangeRequestPolicyRuleInput; + }; +}; + +// The input type for setting the request policies for an existing external canister. +type ExternalCanisterRequestPoliciesUpdateInput = record { + // The request policy rules for the canister change operation. + change : opt vec ExternalCanisterChangeRequestPolicyRuleInput; + // The request policy rules for the calling methods on the canister. + calls : opt ExternalCanisterChangeCallRequestPoliciesInput; +}; + +// An external canister that the station can interact with. +type ExternalCanister = record { + // The id of the resource in the station. + id : UUID; + // The principal id of the canister. + canister_id : principal; + // The name of the canister. + name : text; + // A description that can be used to describe the canister. + description : opt text; + // The labels that can be used to categorize the canister. + labels : vec text; + // The metadata that is associated with the canister. + metadata : vec ExternalCanisterMetadata; + // The current state of the record (e.g. `Active`). + state : ExternalCanisterState; + // The permissions that are set for who can interact with the canister. + permissions : ExternalCanisterPermissions; + // The request policies that are associated with the canister. + request_policies : ExternalCanisterRequestPolicies; + // The time at which the canister was created. + created_at : TimestampRFC3339; + // The time at which the canister was last modified, if available. + modified_at : opt TimestampRFC3339; + // Monitoring configuration for the canister. + monitoring : opt MonitorExternalCanisterStartInput; +}; + +// The state of the external canister. +type ExternalCanisterState = variant { + // The record is active and can be interacted with. + Active; + // The record is archived and can no longer be interacted with. + Archived; +}; + +// Input type for getting a external canister. +type GetExternalCanisterInput = record { + // The principal id of the external canister. + canister_id : principal; +}; + +// The caller privileges for the external canister methods. +type ExternalCanisterCallerMethodsPrivileges = record { + // The validation method that is used to validate the request and + // render the argument. + validation_method : ValidationMethodResourceTarget; + // The method that the caller can call on the external canister. + // + // The `*` method name is used to represent that the caller can + // call any method on the canister. + execution_method : text; +}; + +// The caller privileges for the external canister. +type ExternalCanisterCallerPrivileges = record { + // The external canister entry id. + id : UUID; + // The canister id. + canister_id : principal; + // Whether or not the caller can edit the external canister. + can_change : bool; + // Whether or not the caller can fund the external canister. + can_fund : bool; + // The list of methods that the caller can call on the external canister. + can_call : vec ExternalCanisterCallerMethodsPrivileges; +}; + +type GetExternalCanisterResult = variant { + Ok : record { + // The external canister that was retrieved. + canister : ExternalCanister; + // The caller privileges for the external canister. + privileges : ExternalCanisterCallerPrivileges; + }; + Err : Error; +}; + +// The input type for sorting the results of listing external canisters. +type ListExternalCanistersSortInput = variant { + // Sort by the name of the external canister. + Name : SortByDirection; +}; + +// Input type for listing external canisters with the given filters. +type ListExternalCanistersInput = record { + // The principal id of the external canister to search for. + canister_ids : opt vec principal; + // The labels to use for filtering the external canisters. + labels : opt vec text; + // The current state of the external canisters to use for filtering (e.g. `Active`, `Archived`). + states : opt vec ExternalCanisterState; + // The pagination parameters. + paginate : opt PaginationInput; + // The sort parameters. + sort_by : opt ListExternalCanistersSortInput; +}; + +// Result type for listing external canisters. +type ListExternalCanistersResult = variant { + // The result data for a successful execution. + Ok : record { + // The list of external canisters. + canisters : vec ExternalCanister; + // The offset to use for the next page. + next_offset : opt nat64; + // The total number of external canisters. + total : nat64; + // The caller privileges for the external canisters. + privileges : vec ExternalCanisterCallerPrivileges; + }; + // The error that occurred (e.g. the user does not have the necessary permissions). + Err : Error; +}; + +// The input type for getting the available filters for the external canisters. +type GetExternalCanisterFiltersInput = record { + // Include the names of the external canisters in the result. + with_name : opt record { + // The prefix to use for filtering the names. + // + // If the prefix is not provided, any name will be returned. + prefix : opt text; + }; + // Include the labels of the external canisters in the result. + with_labels : opt bool; +}; + +// The result type for the filtering of external canisters. +type GetExternalCanisterFiltersResult = variant { + Ok : record { + // The list of names that are used by the external canisters + // and their canister id. + names : opt vec record { name : text; canister_id : principal }; + // The list of labels that are used by the external canisters. + labels : opt vec text; + }; + Err : Error; +}; + +type CanisterSnapshotsInput = record { + canister_id : principal; +}; + +type CanisterSnapshotsResponse = vec record { + snapshot_id : text; + taken_at_timestamp : TimestampRFC3339; + total_size : nat64; +}; + +type CanisterSnapshotsResult = variant { + Ok : CanisterSnapshotsResponse; + Err : Error; +}; + +type HeaderField = record { text; text }; + +type HttpRequest = record { + method : text; + url : text; + headers : vec HeaderField; + body : blob; +}; + +type HttpResponse = record { + status_code : nat16; + headers : vec HeaderField; + body : blob; +}; + +type NotifyFailedStationUpgradeInput = record { + // the failure reason + reason : text; +}; + +type NotifyFailedStationUpgradeResult = variant { + Ok; + Err : Error; +}; + +// The Station service definition. +service : (opt SystemInstall) -> { + // Check if the station is healthy and ready to be used. + health_status : () -> (HealthStatus) query; + // Get the system information of the canister (e.g. version, cycles, etc.). + // + // This method contains sensitive information and is up to the canister owner to + // decide who can access it (e.g. only admins). + system_info : () -> (SystemInfoResult) query; + // This method exposes the supported assets and other capabilities of the canister. + // + // By default can be accessed by any active user. + capabilities : () -> (CapabilitiesResult) query; + // Get the authenticated user and its privileges from the caller. + me : () -> (MeResult) query; + // Get the list of notifications associated with the caller. + list_notifications : (input : ListNotificationsInput) -> (ListNotificationsResult) query; + // Mark the notifications as read. + mark_notifications_read : (input : MarkNotificationsReadInput) -> (MarkNotificationReadResult); + // Get the external canister by its canister id. + get_external_canister : (input : GetExternalCanisterInput) -> (GetExternalCanisterResult) query; + // List all external canisters that the caller has access to. + list_external_canisters : (input : ListExternalCanistersInput) -> (ListExternalCanistersResult) query; + // Get the available filters for the external canisters. + get_external_canister_filters : (input : GetExternalCanisterFiltersInput) -> (GetExternalCanisterFiltersResult) query; + // Get a account by id. + // + // If the caller does not have access to the account, an error will be returned. + get_account : (input : GetAccountInput) -> (GetAccountResult) query; + // Get the account balance. + // + // If the caller does not have access to the account, an error will be returned. + fetch_account_balances : (input : FetchAccountBalancesInput) -> (FetchAccountBalancesResult); + // List all accounts that the caller has access to. + // + // If the caller is not the owner of any account, an error will be returned. + list_accounts : (input : ListAccountsInput) -> (ListAccountsResult) query; + // List all transfers from the requested account. + list_account_transfers : (input : ListAccountTransfersInput) -> (ListAccountTransfersResult) query; + // Get transfers by their ids. + get_transfers : (input : GetTransfersInput) -> (GetTransfersResult) query; + // If the caller does not have access to the address book entry, an error will be returned. + get_address_book_entry : (input : GetAddressBookEntryInput) -> (GetAddressBookEntryResult) query; + // List all address book entries for a given blockchain standard. + list_address_book_entries : (input : ListAddressBookEntriesInput) -> (ListAddressBookEntriesResult) query; + // Create a new request. + // + // The request will be created and the caller will be added as the requester. + create_request : (input : CreateRequestInput) -> (CreateRequestResult); + // Cancel a request if the request is in a cancelable state. + // + // Cancelable conditions: + // + // - The request is in the `Created` state. + // - The caller is the requester of the request. + cancel_request : (input : CancelRequestInput) -> (CancelRequestResult); + // Get the list of requests. + // + // Only requests that the caller has access to will be returned. + list_requests : (input : ListRequestsInput) -> (ListRequestsResult) query; + // Get the request by id. + get_request : (input : GetRequestInput) -> (GetRequestResult) query; + // Finds the next aprovable request for the caller. + get_next_approvable_request : (input : GetNextApprovableRequestInput) -> (GetNextApprovableRequestResult) query; + // Submits the user approval decision for a request. + submit_request_approval : (input : SubmitRequestApprovalInput) -> (SubmitRequestApprovalResult); + // Get the user associated with the user id provided. + get_user : (input : GetUserInput) -> (GetUserResult) query; + // List all users of the station. + list_users : (input : ListUsersInput) -> (ListUsersResult) query; + // List all permissions. + list_permissions : (input : ListPermissionsInput) -> (ListPermissionsResult) query; + // Get the permission for the resource provided. + get_permission : (input : GetPermissionInput) -> (GetPermissionResult) query; + // List add request policies. + list_request_policies : (input : ListRequestPoliciesInput) -> (ListRequestPoliciesResult) query; + // Get request policy by id. + get_request_policy : (input : GetRequestPolicyInput) -> (GetRequestPolicyResult) query; + // Get a user group by id. + // + // If the caller does not have access to the user group, an error will be returned. + get_user_group : (input : GetUserGroupInput) -> (GetUserGroupResult) query; + // List all user groups of the station. + list_user_groups : (input : ListUserGroupsInput) -> (ListUserGroupsResult) query; + // Get canister status of a canister controlled by the station. + canister_status : (input : CanisterStatusInput) -> (CanisterStatusResponse); + // Get snapshots of a canister controlled by the station. + canister_snapshots : (input : CanisterSnapshotsInput) -> (CanisterSnapshotsResult); + // HTTP Protocol interface. + http_request : (HttpRequest) -> (HttpResponse) query; + // Internal endpoint used by the upgrader canister to notify the station about a failed station upgrade request. + notify_failed_station_upgrade : (NotifyFailedStationUpgradeInput) -> (NotifyFailedStationUpgradeResult); + // Get an asset by id. + get_asset : (input : GetAssetInput) -> (GetAssetResult) query; + // List all assets that the caller has access to. + list_assets : (input : ListAssetsInput) -> (ListAssetsResult) query; + // Get a named rule by id. + get_named_rule : (input : GetNamedRuleInput) -> (GetNamedRuleResult) query; + // List named rules that the caller has access to. + list_named_rules : (input : ListNamedRulesInput) -> (ListNamedRulesResult) query; +}; diff --git a/cli/src/audit/generated/station.did.d.ts b/cli/src/audit/generated/station.did.d.ts new file mode 100644 index 000000000..47a4f7dd4 --- /dev/null +++ b/cli/src/audit/generated/station.did.d.ts @@ -0,0 +1,5925 @@ +import type { Principal } from '@dfinity/principal'; +import type { ActorMethod } from '@dfinity/agent'; +import type { IDL } from '@dfinity/candid'; + +/** + * A record type that can be used to represent a account in the canister. + */ +export interface Account { + /** + * The internal account id. + */ + 'id' : UUID, + /** + * The configs approval policy for the account. + * + * The configs approval policy defines the rule that must be met for the account to have its configs updated. + */ + 'configs_request_policy' : [] | [RequestPolicyRule], + /** + * Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`). + */ + 'metadata' : Array, + /** + * A friendly name for the account. + */ + 'name' : string, + /** + * The list of assets supported by this account. + */ + 'assets' : Array, + /** + * The list of addresses associated with the account. + */ + 'addresses' : Array, + /** + * The transfer approval policy for the account. + * + * The transfer approval policy defines the rule that must be met for a transfer to be approved. + */ + 'transfer_request_policy' : [] | [RequestPolicyRule], + /** + * The time at which the account was created or last modified (e.g. "2021-01-01T00:00:00Z"). + */ + 'last_modification_timestamp' : TimestampRFC3339, +} +/** + * Record type to describe an address of an account. + */ +export interface AccountAddress { + /** + * The address. + */ + 'address' : string, + /** + * The format of the address, eg. icp_account_identifier. + */ + 'format' : string, +} +/** + * Record type to describe an asset of an account. + */ +export interface AccountAsset { + /** + * The balance of the asset. + */ + 'balance' : [] | [AccountBalance], + /** + * The asset id. + */ + 'asset_id' : UUID, +} +export interface AccountBalance { + /** + * The account id. + */ + 'account_id' : UUID, + /** + * The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.). + */ + 'decimals' : number, + /** + * The balance of the account. + */ + 'balance' : bigint, + /** + * The time at which the balance was last updated. + */ + 'last_update_timestamp' : TimestampRFC3339, + /** + * The state of balance query: + * - `fresh`: The balance was recently updated and is considered fresh. + * - `stale`: The balance may be out of date. + * - `stale_refreshing`: The balance may be out of date but it is being refreshed in the background. + */ + 'query_state' : string, + /** + * The asset id. + */ + 'asset_id' : UUID, +} +/** + * A record type that can be used to represent a account balance. + */ +export interface AccountBalanceInfo { + /** + * The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.). + */ + 'decimals' : number, + /** + * Balance of the account. + */ + 'balance' : bigint, + /** + * The time at which the balance was last updated. + */ + 'last_update_timestamp' : TimestampRFC3339, +} +/** + * A record type that can be used to represent the privileges of a caller for a given account. + */ +export interface AccountCallerPrivileges { + /** + * The account id that the caller has privileges for. + */ + 'id' : UUID, + /** + * Whether or not the caller can request transfers from the account. + */ + 'can_transfer' : boolean, + /** + * Whether or not the caller can edit the account. + */ + 'can_edit' : boolean, +} +/** + * Account can have additional information attached to them, + * this type can be used to represent the additional info. + */ +export interface AccountMetadata { + /** + * The key of the additional info (e.g. "contract") + */ + 'key' : string, + /** + * The value of the additional info (e.g. "0x1234") + */ + 'value' : string, +} +/** + * The actions that are available for accounts. + */ +export type AccountResourceAction = { 'List' : null } | + { 'Read' : ResourceId } | + { 'Create' : null } | + { 'Transfer' : ResourceId } | + { 'Update' : ResourceId }; +/** + * The seed used to derive the addresses of the account. + */ +export type AccountSeed = Uint8Array | number[]; +export interface AddAccountOperation { + /** + * The account, only available after the request is executed. + */ + 'account' : [] | [Account], + /** + * The input to the request to add the account. + */ + 'input' : AddAccountOperationInput, +} +/** + * Input type for adding an account through a request. + */ +export interface AddAccountOperationInput { + /** + * The approval policy for updates to the account. + */ + 'configs_request_policy' : [] | [RequestPolicyRule], + /** + * Who can read the account information. + */ + 'read_permission' : Allow, + /** + * Who can request updates to the account. + */ + 'configs_permission' : Allow, + /** + * Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`). + */ + 'metadata' : Array, + /** + * A friendly name for the account (e.g. "My Account"). + */ + 'name' : string, + /** + * The assets to add to the account. + */ + 'assets' : Array, + /** + * The approval policy for transfers from the account. + */ + 'transfer_request_policy' : [] | [RequestPolicyRule], + /** + * Who can request transfers from the account. + */ + 'transfer_permission' : Allow, +} +export interface AddAddressBookEntryOperation { + /** + * The address book entry, only available after the request is executed. + */ + 'address_book_entry' : [] | [AddressBookEntry], + /** + * The input to the request to add the address book entry. + */ + 'input' : AddAddressBookEntryOperationInput, +} +/** + * Input type for creating a new address book entry through a request. + */ +export interface AddAddressBookEntryOperationInput { + /** + * Metadata associated with the address book entry (e.g. `{"kyc": "true"}`). + */ + 'metadata' : Array, + /** + * The labels associated with the address book entry (e.g. `["exchange", "kyc"]`). + */ + 'labels' : Array, + /** + * The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + */ + 'blockchain' : string, + /** + * The actual address. + */ + 'address' : string, + /** + * The format of the address, eg. icp_account_identifier + */ + 'address_format' : string, + /** + * The owner of the address. + */ + 'address_owner' : string, +} +export interface AddAssetOperation { + /** + * The result of adding an asset. + */ + 'asset' : [] | [Asset], + /** + * The input to the request to add an asset. + */ + 'input' : AddAssetOperationInput, +} +/** + * The input type for adding an asset. + */ +export interface AddAssetOperationInput { + /** + * The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.). + */ + 'decimals' : number, + /** + * The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string + * with spaces replaced with underscores. + */ + 'standards' : Array, + /** + * The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`). + */ + 'metadata' : Array, + /** + * The asset name (e.g. `Internet Computer`, `Bitcoin`, `Ethereum`, etc.) + */ + 'name' : string, + /** + * The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + */ + 'blockchain' : string, + /** + * The asset symbol, e.g. "ICP" or "BTC". + */ + 'symbol' : AssetSymbol, +} +/** + * The operation type for adding a new named rule. + */ +export interface AddNamedRuleOperation { + /** + * The result of adding a named rule. + */ + 'named_rule' : [] | [NamedRule], + /** + * The input to the request to add a named rule. + */ + 'input' : AddNamedRuleOperationInput, +} +/** + * The input type for creating a named rule. + */ +export interface AddNamedRuleOperationInput { + /** + * The rule name. + */ + 'name' : string, + /** + * The rule value. + */ + 'rule' : RequestPolicyRule, + /** + * The rule description. + */ + 'description' : [] | [string], +} +export interface AddRequestPolicyOperation { + /** + * The input to the request to add a request policy. + */ + 'input' : AddRequestPolicyOperationInput, + /** + * The request policy that was created by the request (only available after the request is executed). + */ + 'policy_id' : [] | [UUID], +} +export interface AddRequestPolicyOperationInput { + /** + * The rule to use for the request evaluation. + */ + 'rule' : RequestPolicyRule, + /** + * The request specifier that identifies the request to add a policy for. + */ + 'specifier' : RequestSpecifier, +} +export interface AddUserGroupOperation { + /** + * The user group that was added, only available after the request is executed. + */ + 'user_group' : [] | [UserGroup], + /** + * The input to the request to add the user group. + */ + 'input' : AddUserGroupOperationInput, +} +export interface AddUserGroupOperationInput { + /** + * The name of the group. + */ + 'name' : string, +} +export interface AddUserOperation { + /** + * The user that was added, only available after the request is executed. + */ + 'user' : [] | [User], + /** + * The input to the request to add the user. + */ + 'input' : AddUserOperationInput, +} +export interface AddUserOperationInput { + /** + * The status of the user (e.g. `Active`). + * + * The user must be active to be able to practically use the station. + */ + 'status' : UserStatus, + /** + * The list of groups the user belongs to. + * + * Users can be tagged with groups that can be used to control access to the station + * (e.g. the UUID of the finance group). + */ + 'groups' : Array, + /** + * The user name (e.g. "John Doe"). + */ + 'name' : string, + /** + * The principals associated with the user. + */ + 'identities' : Array, +} +/** + * A record type that can be used to represent an address book entry in the canister. + */ +export interface AddressBookEntry { + /** + * The internal address book entry id. + */ + 'id' : UUID, + /** + * Metadata associated with the address book entry (e.g. `{"kyc": "true"}`). + */ + 'metadata' : Array, + /** + * The list of labels associated with the address book entry (e.g. `["kyc", "approved"]`). + */ + 'labels' : Array, + /** + * The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + */ + 'blockchain' : string, + /** + * The actual address. + */ + 'address' : string, + /** + * The time at which the address book entry was created or last modified (e.g. "2021-01-01T00:00:00Z"). + */ + 'last_modification_timestamp' : string, + /** + * The address format (e.g. "icp_account_identifier"). + */ + 'address_format' : string, + /** + * The address owner. + */ + 'address_owner' : string, +} +/** + * A record type that can be used to represent the privileges of a caller for a given address book entry. + */ +export interface AddressBookEntryCallerPrivileges { + /** + * The address book entry id. + */ + 'id' : UUID, + /** + * Whether or not the caller can delete the address book entry. + */ + 'can_delete' : boolean, + /** + * Whether or not the caller can edit the address book entry. + */ + 'can_edit' : boolean, +} +/** + * Address book entries can have additional information attached to them, + * this type can be used to represent the additional info. + */ +export interface AddressBookMetadata { + /** + * The key of the additional info (e.g. "kyc") + */ + 'key' : string, + /** + * The value of the additional info (e.g. "true") + */ + 'value' : string, +} +/** + * The allow rules for who can access the resource. + */ +export interface Allow { + /** + * Only the specified user groups can access the resource. + */ + 'user_groups' : Array, + /** + * Required authentication level for accessing the resource. + */ + 'auth_scope' : AuthScope, + /** + * Only the specified users can access the resource. + */ + 'users' : Array, +} +/** + * A record type that can be used to represent an asset in the station. + */ +export interface Asset { + /** + * The internal asset id. + */ + 'id' : UUID, + /** + * The number of decimals used by the asset (e.g. `8` for `BTC`, `18` for `ETH`, etc.). + */ + 'decimals' : number, + /** + * The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string + * with spaces replaced with underscores. + */ + 'standards' : Array, + /** + * The asset metadata (e.g. `{"logo": "https://example.com/logo.png"}`). + */ + 'metadata' : Array, + /** + * The asset name (e.g. `Internet Computer`, `Bitcoin`, `Ethereum`, etc.) + */ + 'name' : string, + /** + * The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + */ + 'blockchain' : string, + /** + * The asset symbol, e.g. "ICP" or "BTC". + */ + 'symbol' : AssetSymbol, +} +export interface AssetCallerPrivileges { + 'id' : UUID, + 'can_delete' : boolean, + 'can_edit' : boolean, +} +/** + * Assets can have additional information attached to them, + * this type can be used to represent the additional info. + */ +export interface AssetMetadata { + /** + * The key of the additional info (e.g. "logo") + */ + 'key' : string, + /** + * The value of the additional info (e.g. "https://example.com/logo.png") + */ + 'value' : string, +} +/** + * The asset symbol, e.g. "ICP" or "BTC". + */ +export type AssetSymbol = string; +/** + * The authorization scope the caller must have, used to specify the required scope for accessing a resource. + */ +export type AuthScope = { + /** + * Requires to be an authenticated user to access the resource. + */ + 'Authenticated' : null + } | + { + /** + * Allows access to the resource without requiring any authentication. + */ + 'Public' : null + } | + { + /** + * Requires the caller to have direct access to the resource through user groups or user ids. + */ + 'Restricted' : null + }; +/** + * A basic user record that can be used to represent a user in the station. + */ +export interface BasicUser { + /** + * The UUID of the user (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + */ + 'id' : UUID, + /** + * The status of the user (e.g. `Active`). + */ + 'status' : UserStatus, + /** + * The user name (e.g. "John Doe"). + */ + 'name' : string, +} +export interface CallExternalCanisterOperation { + /** + * This field is not populated in list responses, only when using `get_request` and + * setting `with_full_info` to `opt true` to avoid going over the response size limit. + */ + 'arg' : [] | [Uint8Array | number[]], + /** + * see `CallExternalCanisterOperationInput` + */ + 'execution_method' : CanisterMethod, + /** + * see `CallExternalCanisterOperationInput` + */ + 'validation_method' : [] | [CanisterMethod], + /** + * The checksum of the argument blob passed to both the validation and execution method. + * Defaults to `null` if no argument blob is provided. + */ + 'arg_checksum' : [] | [Sha256Hash], + /** + * The amount of cycles attached to the call of the execution method. + */ + 'execution_method_cycles' : [] | [bigint], + /** + * A human-readable rendering of the argument blob procuded by the validation method. + */ + 'arg_rendering' : [] | [string], + /** + * The reply blob produced by a successful call of the execution method, + * i.e., when the request is `Completed`. + */ + 'execution_method_reply' : [] | [Uint8Array | number[]], +} +export interface CallExternalCanisterOperationInput { + /** + * The argument blob passed to both the validation and execution method. + * Defaults to the candid encoding of '()' if omitted. + */ + 'arg' : [] | [Uint8Array | number[]], + /** + * The canister method that is called after the request becomes `Approved` + * passing the validated argument blob. + */ + 'execution_method' : CanisterMethod, + /** + * The canister method validating the argument blob: + * - on validation success, returns a human-readable rendering of the argument blob + * and then the request becomes `Created`; + * - on validation error, returns a textual diagnostic message + * and then the request creation fails with a validation error + * containing the textual diagnostic message. + * Formally, the return type of the validation method must be + * ``` + * variant { + * Ok : text; + * Err : text; + * } + * ``` + * If omitted (`validation_method = null`), no validation of the argument blob is performed + * and no human-readable rendering of the argument blob is provided. + */ + 'validation_method' : [] | [CanisterMethod], + /** + * The amount of cycles attached to the call of the execution method. + */ + 'execution_method_cycles' : [] | [bigint], +} +/** + * The validation and execution method targets of a `CallExternalCanister` request. + */ +export interface CallExternalCanisterResourceTarget { + 'execution_method' : ExecutionMethodResourceTarget, + 'validation_method' : ValidationMethodResourceTarget, +} +/** + * The input type for canceling a request. + */ +export interface CancelRequestInput { + /** + * The request id to cancel. + */ + 'request_id' : UUID, + /** + * The reason for canceling the request. + */ + 'reason' : [] | [string], +} +/** + * The result type for canceling a request. + */ +export type CancelRequestResult = { + 'Ok' : { + /** + * The request that was canceled. + */ + 'request' : Request, + } + } | + { 'Err' : Error }; +/** + * The pair that is used to represent the execution and validation method. + */ +export interface CanisterExecutionAndValidationMethodPair { + /** + * The method that the caller can call on the external canister. + * + * The `*` method name is used to represent that the caller can call any method on the canister. + */ + 'execution_method' : string, + /** + * The validation method that is used to validate the request and + * render the argument. + */ + 'validation_method' : ValidationMethodResourceTarget, +} +export type CanisterInstallMode = { 'reinstall' : null } | + { + /** + * Upgrade an existing canister. The optional record mirrors the IC + * management canister's `CanisterUpgradeOptions`. + * `wasm_memory_persistence = keep` is required for Motoko canisters that + * use Enhanced Orthogonal Persistence; otherwise the IC clears their main + * memory. + */ + 'upgrade' : [] | [ + { + 'wasm_memory_persistence' : [] | [ + { 'keep' : null } | + { 'replace' : null } + ], + 'skip_pre_upgrade' : [] | [boolean], + } + ] + } | + { 'install' : null }; +export interface CanisterMethod { + /** + * The canister to call. + */ + 'canister_id' : Principal, + /** + * The method to call on the canister. + */ + 'method_name' : string, +} +export interface CanisterSnapshotsInput { 'canister_id' : Principal } +export type CanisterSnapshotsResponse = Array< + { + 'total_size' : bigint, + 'taken_at_timestamp' : TimestampRFC3339, + 'snapshot_id' : string, + } +>; +export type CanisterSnapshotsResult = { 'Ok' : CanisterSnapshotsResponse } | + { 'Err' : Error }; +export interface CanisterStatusInput { 'canister_id' : Principal } +export interface CanisterStatusResponse { + 'status' : { 'stopped' : null } | + { 'stopping' : null } | + { 'running' : null }, + 'memory_size' : bigint, + 'cycles' : bigint, + 'settings' : DefiniteCanisterSettings, + 'query_stats' : { + 'response_payload_bytes_total' : bigint, + 'num_instructions_total' : bigint, + 'num_calls_total' : bigint, + 'request_payload_bytes_total' : bigint, + }, + 'idle_cycles_burned_per_day' : bigint, + 'module_hash' : [] | [Uint8Array | number[]], + 'reserved_cycles' : bigint, +} +/** + * A record type that is used to show the current capabilities of the station. + */ +export interface Capabilities { + /** + * The name of the station. + */ + 'name' : string, + /** + * Version of the station. + */ + 'version' : string, + /** + * The list of supported assets. + */ + 'supported_assets' : Array, + /** + * The list of supported blockchains and standards. + */ + 'supported_blockchains' : Array, +} +/** + * Result type for getting the current config. + */ +export type CapabilitiesResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The config. + */ + 'capabilities' : Capabilities, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Type for instructions to update the address book entry's metadata. + */ +export type ChangeAddressBookMetadata = { + /** + * Override values of existing metadata with the specified keys + * and add new metadata if no metadata can be found with the specified keys. + */ + 'OverrideSpecifiedBy' : Array + } | + { + /** + * Remove metadata with the specified keys. + */ + 'RemoveKeys' : Array + } | + { + /** + * Replace all existing metadata by the specified metadata. + */ + 'ReplaceAllBy' : Array + }; +/** + * Mutate the list of assets. + */ +export type ChangeAssets = { + /** + * Replace all current assets with the specified list. + */ + 'ReplaceWith' : { 'assets' : Array } + } | + { + /** + * Change the list of assets by adding and removing assets. + */ + 'Change' : { 'add_assets' : Array, 'remove_assets' : Array } + }; +/** + * Type for instructions to update the external canister's metadata. + */ +export type ChangeExternalCanisterMetadata = { + /** + * Override values of existing metadata with the specified keys + * and add new metadata if no metadata can be found with the specified keys. + */ + 'OverrideSpecifiedBy' : Array + } | + { + /** + * Remove metadata with the specified keys. + */ + 'RemoveKeys' : Array + } | + { + /** + * Replace all existing metadata by the specified metadata. + */ + 'ReplaceAllBy' : Array + }; +export interface ChangeExternalCanisterOperation { + /** + * The canister installation mode. + */ + 'mode' : CanisterInstallMode, + /** + * The canister to install. + */ + 'canister_id' : Principal, + /** + * The checksum of the wasm module. + */ + 'module_checksum' : Sha256Hash, + /** + * The checksum of the arg blob. + */ + 'arg_checksum' : [] | [Sha256Hash], +} +export interface ChangeExternalCanisterOperationInput { + /** + * The initial argument passed to the new wasm module. + */ + 'arg' : [] | [Uint8Array | number[]], + /** + * Additional wasm module chunks to append to the wasm module. + */ + 'module_extra_chunks' : [] | [WasmModuleExtraChunks], + /** + * The canister installation mode. + */ + 'mode' : CanisterInstallMode, + /** + * The canister to install. + */ + 'canister_id' : Principal, + /** + * The wasm module to install. + */ + 'module' : Uint8Array | number[], +} +/** + * Type for instructions to update the address book entry's metadata. + */ +export type ChangeMetadata = { + /** + * Override values of existing metadata with the specified keys + * and add new metadata if no metadata can be found with the specified keys. + */ + 'OverrideSpecifiedBy' : Array + } | + { + /** + * Remove metadata with the specified keys. + */ + 'RemoveKeys' : Array + } | + { + /** + * Replace all existing metadata by the specified metadata. + */ + 'ReplaceAllBy' : Array + }; +export type ConfigureExternalCanisterOperation = ConfigureExternalCanisterOperationInput; +export interface ConfigureExternalCanisterOperationInput { + /** + * The kind of operation to perform. + */ + 'kind' : ConfigureExternalCanisterOperationKind, + /** + * The canister to update. + */ + 'canister_id' : Principal, +} +/** + * The input type for configuring an external canister in the station. + */ +export type ConfigureExternalCanisterOperationKind = { + /** + * Remove the canister from the Station only. + */ + 'SoftDelete' : null + } | + { + /** + * The settings to configure for the external canister. + */ + 'Settings' : ConfigureExternalCanisterSettingsInput + } | + { + /** + * Remove the canister from the Station and the IC. + * + * Caution: This operation is irreversible. + */ + 'Delete' : null + } | + { + /** + * The Internet Computer canister settings to configure for the external canister. + */ + 'NativeSettings' : DefiniteCanisterSettingsInput + }; +export interface ConfigureExternalCanisterSettingsInput { + /** + * What operations are allowed on the canister. + */ + 'permissions' : [] | [ExternalCanisterPermissionsUpdateInput], + 'name' : [] | [string], + /** + * The labels of the external canister. + */ + 'labels' : [] | [Array], + /** + * The description of the external canister. + */ + 'description' : [] | [string], + /** + * The request policies for the canister. + */ + 'request_policies' : [] | [ExternalCanisterRequestPoliciesUpdateInput], + /** + * The state of the external canister. + */ + 'state' : [] | [ExternalCanisterState], + /** + * The metadata of the external canister. + */ + 'change_metadata' : [] | [ChangeExternalCanisterMetadata], +} +export interface CreateExternalCanisterOperation { + 'canister_id' : [] | [Principal], + 'input' : CreateExternalCanisterOperationInput, +} +export interface CreateExternalCanisterOperationInput { + /** + * What operations are allowed on the canister. + */ + 'permissions' : ExternalCanisterPermissionsCreateInput, + /** + * The metadata of the external canister. + */ + 'metadata' : [] | [Array], + /** + * The kind of create operation to perform. + */ + 'kind' : CreateExternalCanisterOperationKind, + /** + * The name of the external canister. + */ + 'name' : string, + /** + * The labels of the external canister. + */ + 'labels' : [] | [Array], + /** + * The description of the external canister. + */ + 'description' : [] | [string], + /** + * The request policies for the canister. + */ + 'request_policies' : ExternalCanisterRequestPoliciesCreateInput, +} +export type CreateExternalCanisterOperationKind = { + /** + * An existing canister is added to the station. + */ + 'AddExisting' : CreateExternalCanisterOperationKindAddExisting + } | + { + /** + * A new canister is created. + */ + 'CreateNew' : CreateExternalCanisterOperationKindCreateNew + }; +export interface CreateExternalCanisterOperationKindAddExisting { + /** + * The canister id to use. + */ + 'canister_id' : Principal, +} +export interface CreateExternalCanisterOperationKindCreateNew { + /** + * The initial cycles to allocate to the canister. + * + * If not set, only the minimal amount of cycles required to create the + * canister will be allocated. + */ + 'initial_cycles' : [] | [bigint], + /** + * The subnet on which the canister should be created. + * + * By default, the canister is created on the same subnet as the station. + */ + 'subnet_selection' : [] | [SubnetSelection], +} +/** + * The input type for creating a request. + */ +export interface CreateRequestInput { + /** + * The request title (e.g. "Payment to John"). + */ + 'title' : [] | [string], + /** + * The time at which the request will execute if approved. + */ + 'execution_plan' : [] | [RequestExecutionSchedule], + /** + * The time at which the request will expire if still pending. + */ + 'expiration_dt' : [] | [TimestampRFC3339], + /** + * The optional deduplication key used to ensure request uniqueness. + */ + 'deduplication_key' : [] | [string], + /** + * The list of tags for the request. + */ + 'tags' : [] | [Array], + /** + * The request summary (e.g. "This request will transfer 100 ICP to the account 0x1234"). + */ + 'summary' : [] | [string], + /** + * The operation that was requested. + */ + 'operation' : RequestOperationInput, +} +/** + * The result type for creating a request. + */ +export type CreateRequestResult = { + 'Ok' : { + /** + * The privileges of the caller. + */ + 'privileges' : RequestCallerPrivileges, + /** + * The request that was created. + */ + 'request' : Request, + /** + * The additional info about the request. + */ + 'additional_info' : RequestAdditionalInfo, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Strategy defining how the station canister tops up its own cycles. + */ +export type CycleObtainStrategy = { + /** + * Do not obtain cycles for Orbit. + */ + 'Disabled' : null + } | + { + /** + * Use the CMC to mint cycles from ICP held in an Orbit account. + */ + 'MintFromNativeToken' : { + /** + * The Orbit account ID to use for minting cycles. + */ + 'account_id' : UUID, + /** + * The Orbit account name. + */ + 'account_name' : [] | [string], + } + } | + { + /** + * Use the Cycles Ledger balance to obtain cycles. + */ + 'WithdrawFromCyclesLedger' : { + /** + * The Orbit account ID to use for obtaining cycles. + */ + 'account_id' : UUID, + /** + * The Orbit account name. + */ + 'account_name' : [] | [string], + } + }; +/** + * Strategy defining how the station canister tops up its own cycles. + */ +export type CycleObtainStrategyInput = { + /** + * Do not obtain cycles for Orbit. + */ + 'Disabled' : null + } | + { + /** + * Use the CMC to mint cycles from ICP held in an Orbit account. + */ + 'MintFromNativeToken' : { + /** + * The Orbit account ID to use for minting cycles. + */ + 'account_id' : UUID, + } + } | + { + /** + * Use the Cycles Ledger balance to obtain cycles. + */ + 'WithdrawFromCyclesLedger' : { + /** + * The Orbit account ID to use for obtaining cycles. + */ + 'account_id' : UUID, + } + }; +export interface DefiniteCanisterSettings { + 'freezing_threshold' : bigint, + 'controllers' : Array, + 'reserved_cycles_limit' : bigint, + 'log_visibility' : LogVisibility, + 'wasm_memory_limit' : bigint, + 'memory_allocation' : bigint, + 'compute_allocation' : bigint, +} +export interface DefiniteCanisterSettingsInput { + 'freezing_threshold' : [] | [bigint], + 'controllers' : [] | [Array], + 'reserved_cycles_limit' : [] | [bigint], + 'log_visibility' : [] | [LogVisibility], + 'wasm_memory_limit' : [] | [bigint], + 'memory_allocation' : [] | [bigint], + 'compute_allocation' : [] | [bigint], +} +/** + * The disaster recovery committee extended with the user group name. + */ +export interface DisasterRecovery { + /** + * The name of the disaster recovery committee user group. + */ + 'user_group_name' : [] | [string], + /** + * The disaster recovery committee. + */ + 'committee' : DisasterRecoveryCommittee, +} +export interface DisasterRecoveryCommittee { + /** + * The user group id of the committee. + */ + 'user_group_id' : UUID, + /** + * The quorum required for the committee to approve a disaster recovery operation. + */ + 'quorum' : number, +} +/** + * A minimal user record that is meant to aid displaying users on the client. + */ +export interface DisplayUser { + /** + * The UUID of the user (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + */ + 'id' : UUID, + /** + * The user name (e.g. "John Doe"). + */ + 'name' : string, +} +export interface EditAccountOperation { + /** + * The input to the request to edit the account. + */ + 'input' : EditAccountOperationInput, +} +/** + * Input type for editing an account through a request. + */ +export interface EditAccountOperationInput { + /** + * The account id that will be edited. + */ + 'account_id' : UUID, + /** + * The request policy for what it takes to execute a configuration change. + */ + 'configs_request_policy' : [] | [RequestPolicyRuleInput], + /** + * Who can read the account information. + */ + 'read_permission' : [] | [Allow], + /** + * Who can request configuration changes to the account. + */ + 'configs_permission' : [] | [Allow], + /** + * A friendly name for the account (e.g. "My Account"). + */ + 'name' : [] | [string], + /** + * Mutate the list of assets. + */ + 'change_assets' : [] | [ChangeAssets], + /** + * The request policy for what it takes to execute a transfer. + */ + 'transfer_request_policy' : [] | [RequestPolicyRuleInput], + /** + * Who can request transfers from the account. + */ + 'transfer_permission' : [] | [Allow], +} +export interface EditAddressBookEntryOperation { + /** + * The input to the request to edit the address book entry. + */ + 'input' : EditAddressBookEntryOperationInput, +} +/** + * Input type for updating an address book entry through a request. + */ +export interface EditAddressBookEntryOperationInput { + /** + * The updated list of labels associated with the address book entry. + */ + 'labels' : [] | [Array], + /** + * Instructions to update the address book entry's metadata. + */ + 'change_metadata' : [] | [ChangeAddressBookMetadata], + /** + * The id of the address book entry. + */ + 'address_book_entry_id' : UUID, + /** + * The new owner of the address. + */ + 'address_owner' : [] | [string], +} +export interface EditAssetOperation { + /** + * The input to the request to edit an asset. + */ + 'input' : EditAssetOperationInput, +} +/** + * The input type for editing an asset. + */ +export interface EditAssetOperationInput { + /** + * The asset standard that is supported (e.g. `erc20`, etc.), canonically represented as a lowercase string + * with spaces replaced with underscores. + */ + 'standards' : [] | [Array], + /** + * The name of the asset. + */ + 'name' : [] | [string], + /** + * The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + */ + 'blockchain' : [] | [string], + /** + * The metadata to change. + */ + 'change_metadata' : [] | [ChangeMetadata], + /** + * The asset id to edit. + */ + 'asset_id' : UUID, + /** + * The asset symbol, e.g. "ICP" or "BTC". + */ + 'symbol' : [] | [AssetSymbol], +} +/** + * The operation type for editing an existing named rule. + */ +export interface EditNamedRuleOperation { + /** + * The input to the request to edit a named rule. + */ + 'input' : EditNamedRuleOperationInput, +} +/** + * The input type for editing a named rule. + */ +export interface EditNamedRuleOperationInput { + /** + * The rule name. + */ + 'name' : [] | [string], + /** + * The rule value. + */ + 'rule' : [] | [RequestPolicyRule], + /** + * The optional rule description. + */ + 'description' : [] | [[] | [string]], + /** + * The named rule id to edit. + */ + 'named_rule_id' : UUID, +} +export interface EditPermissionOperation { + /** + * The input to the request to edit an permission. + */ + 'input' : EditPermissionOperationInput, +} +export interface EditPermissionOperationInput { + /** + * The updated resource that this policy will apply to. + */ + 'resource' : Resource, + /** + * The updated list of user groups that have access to the resource. + */ + 'user_groups' : [] | [Array], + /** + * The updated authorization scope for the resource. + */ + 'auth_scope' : [] | [AuthScope], + /** + * The updated list of users that have access to the resource. + */ + 'users' : [] | [Array], +} +export interface EditRequestPolicyOperation { + /** + * The input to the request to edit a request policy. + */ + 'input' : EditRequestPolicyOperationInput, +} +export interface EditRequestPolicyOperationInput { + /** + * The updated rule to use for the request evaluation. + */ + 'rule' : [] | [RequestPolicyRule], + /** + * The updated request specifier that identifies the request to add a policy for. + */ + 'specifier' : [] | [RequestSpecifier], + /** + * The request policy id that will be edited. + */ + 'policy_id' : UUID, +} +export interface EditUserGroupOperation { + /** + * The input to the request to edit the user group. + */ + 'input' : EditUserGroupOperationInput, +} +export interface EditUserGroupOperationInput { + /** + * The name of the group. + */ + 'name' : string, + /** + * The id of the group to edit. + */ + 'user_group_id' : UUID, +} +export interface EditUserOperation { + /** + * The input to the request to edit the user. + */ + 'input' : EditUserOperationInput, +} +export interface EditUserOperationInput { + /** + * The id of the user to edit. + */ + 'id' : UUID, + /** + * The status of the user (e.g. `Active`). + */ + 'status' : [] | [UserStatus], + /** + * The list of groups the user belongs to. + * + * Users can be tagged with groups that can be used to control access to the station + * (e.g. "UUID of the finance group"). + */ + 'groups' : [] | [Array], + /** + * Cancel all pending (request status `Created`) requests for this user. + */ + 'cancel_pending_requests' : [] | [boolean], + /** + * The user name (e.g. "John Doe"). + */ + 'name' : [] | [string], + /** + * The principals associated with the user. + */ + 'identities' : [] | [Array], +} +/** + * Generic error type added to responses that can fail. + */ +export interface Error { + /** + * Error code, added as a string to allow for custom error codes. + */ + 'code' : string, + /** + * Error message to be displayed to the user. + */ + 'message' : [] | [string], + /** + * Error details to be displayed to the user. + */ + 'details' : [] | [Array<[string, string]>], +} +/** + * Defines the evaluation data of a request policy rule. + */ +export type EvaluatedRequestPolicyRule = { 'Not' : RequestPolicyRuleResult } | + { + 'Quorum' : { + 'total_possible_approvers' : bigint, + 'min_approved' : bigint, + 'approvers' : Array, + } + } | + { 'AllowListed' : null } | + { + 'QuorumPercentage' : { + 'total_possible_approvers' : bigint, + 'min_approved' : bigint, + 'approvers' : Array, + } + } | + { 'AutoApproved' : null } | + { 'AllOf' : Array } | + { 'AnyOf' : Array } | + { 'AllowListedByMetadata' : { 'metadata' : AddressBookMetadata } }; +/** + * Defines the high level result of evaluating a request policy rule. + */ +export type EvaluationStatus = { 'Approved' : null } | + { 'Rejected' : null } | + { 'Pending' : null }; +/** + * List of reasons why a request can be approved or rejected. + */ +export type EvaluationSummaryReason = { 'AllowList' : null } | + { 'AllowListMetadata' : null } | + { 'AutoApproved' : null } | + { 'ApprovalQuorum' : null }; +/** + * The execution method targets of a `CallExternalCanister` request. + */ +export type ExecutionMethodResourceTarget = { 'Any' : null } | + { 'ExecutionMethod' : CanisterMethod }; +/** + * An external canister that the station can interact with. + */ +export interface ExternalCanister { + /** + * The id of the resource in the station. + */ + 'id' : UUID, + /** + * The permissions that are set for who can interact with the canister. + */ + 'permissions' : ExternalCanisterPermissions, + /** + * The time at which the canister was last modified, if available. + */ + 'modified_at' : [] | [TimestampRFC3339], + /** + * The metadata that is associated with the canister. + */ + 'metadata' : Array, + /** + * The name of the canister. + */ + 'name' : string, + /** + * The labels that can be used to categorize the canister. + */ + 'labels' : Array, + /** + * The principal id of the canister. + */ + 'canister_id' : Principal, + /** + * A description that can be used to describe the canister. + */ + 'description' : [] | [string], + /** + * The time at which the canister was created. + */ + 'created_at' : TimestampRFC3339, + /** + * The request policies that are associated with the canister. + */ + 'request_policies' : ExternalCanisterRequestPolicies, + /** + * The current state of the record (e.g. `Active`). + */ + 'state' : ExternalCanisterState, + /** + * Monitoring configuration for the canister. + */ + 'monitoring' : [] | [MonitorExternalCanisterStartInput], +} +/** + * The permission for making calls to a specific or any external canister method. + */ +export interface ExternalCanisterCallPermission { + /** + * The execution method that the caller can use, + * if `*` is used the caller can use any method. + */ + 'execution_method' : string, + /** + * Allowed users and user groups for the operation. + */ + 'allow' : Allow, + /** + * The validation method that is used to validate the request and + * render the argument. + */ + 'validation_method' : ValidationMethodResourceTarget, +} +/** + * The request policy rule for the canister call operation. + */ +export interface ExternalCanisterCallRequestPolicyRule { + /** + * The method name that the rule is for, + * if `*` is used the rule applies to all methods. + */ + 'execution_method' : string, + /** + * The request policy rule for the canister call operation. + */ + 'rule' : RequestPolicyRule, + /** + * The validation method that is used to match the policy against + * the permission of the resource. + */ + 'validation_method' : ValidationMethodResourceTarget, + /** + * The id of the request policy rule. + */ + 'policy_id' : UUID, +} +/** + * The request policy rule for the canister call operation. + */ +export interface ExternalCanisterCallRequestPolicyRuleInput { + /** + * The method name that the rule is for, + * if `*` is used the rule applies to all methods. + */ + 'execution_method' : string, + /** + * The request policy rule for the canister call operation. + */ + 'rule' : RequestPolicyRule, + /** + * The validation method that is used to match the policy against + * the permission of the resource. + */ + 'validation_method' : ValidationMethodResourceTarget, + /** + * The id of the request policy rule. + * + * If not provided a new entry will be created. + */ + 'policy_id' : [] | [UUID], +} +/** + * The caller privileges for the external canister methods. + */ +export interface ExternalCanisterCallerMethodsPrivileges { + /** + * The method that the caller can call on the external canister. + * + * The `*` method name is used to represent that the caller can + * call any method on the canister. + */ + 'execution_method' : string, + /** + * The validation method that is used to validate the request and + * render the argument. + */ + 'validation_method' : ValidationMethodResourceTarget, +} +/** + * The caller privileges for the external canister. + */ +export interface ExternalCanisterCallerPrivileges { + /** + * The external canister entry id. + */ + 'id' : UUID, + /** + * Whether or not the caller can edit the external canister. + */ + 'can_change' : boolean, + /** + * The canister id. + */ + 'canister_id' : Principal, + /** + * The list of methods that the caller can call on the external canister. + */ + 'can_call' : Array, + /** + * Whether or not the caller can fund the external canister. + */ + 'can_fund' : boolean, +} +/** + * The input type for setting call permissions of an existing external canister. + */ +export type ExternalCanisterChangeCallPermissionsInput = { + /** + * Override the call permissions from the specified execution methods. + */ + 'OverrideSpecifiedByExecutionMethods' : Array< + { + /** + * The method that the caller can call on the external canister. + * + * The `*` method name is used to represent that the caller can call any method on the canister. + */ + 'execution_method' : string, + /** + * The permissions associated with the execution method, if the list is empty all call permissions of the + * execution method will be removed. + */ + 'permissions' : Array< + { + /** + * Allowed users and user groups for the operation. + */ + 'allow' : Allow, + /** + * The validation method that is used to validate the request and render the argument. + */ + 'validation_method' : ValidationMethodResourceTarget, + } + >, + } + > + } | + { + /** + * Override the permissions for the specified execution and validation method pairs. + */ + 'OverrideSpecifiedByExecutionValidationMethodPairs' : Array< + { + /** + * If allow is not provided the call permission will be removed for the specified execution + * and validation method pair. + */ + 'allow' : [] | [Allow], + /** + * The method configuration that is used to represent the execution and validation method pair. + */ + 'method_configuration' : CanisterExecutionAndValidationMethodPair, + } + > + } | + { + /** + * Replaces all the call permissions with the provided list, if the list is empty + * all the call permissions will be removed. + */ + 'ReplaceAllBy' : Array + }; +export type ExternalCanisterChangeCallRequestPoliciesInput = { + /** + * Remove call request policies by the provided ids. + */ + 'RemoveByPolicyIds' : Array + } | + { + /** + * Override the request policies for the specified execution methods. + */ + 'OverrideSpecifiedByExecutionMethods' : Array< + { + /** + * The method that the caller can call on the external canister. + * + * The `*` method name is used to represent that the caller can call any method on the canister. + */ + 'execution_method' : string, + /** + * The request policies associated with the execution method, if the list is empty all the policies of + * the execution method will be removed. + */ + 'policies' : Array< + { + /** + * The request policy rule for the canister call operation. + */ + 'rule' : RequestPolicyRule, + /** + * The validation method that is used to match the policy against + * the permission of the resource. + */ + 'validation_method' : ValidationMethodResourceTarget, + /** + * The id of the request policy rule. + * + * If not provided a new entry will be created. + */ + 'policy_id' : [] | [UUID], + } + >, + } + > + } | + { + /** + * Override the request policies for the specified execution and validation method pairs. + */ + 'OverrideSpecifiedByExecutionValidationMethodPairs' : Array< + { + /** + * The method configuration that is used to represent the execution and validation method pair. + */ + 'method_configuration' : CanisterExecutionAndValidationMethodPair, + /** + * The request policies to use for the method configuration, if the list is empty all the policies of + * the execution and validation method pair will be removed. + */ + 'policies' : Array, + } + > + } | + { + /** + * Replaces all the call request policies with the provided list. + */ + 'ReplaceAllBy' : Array + }; +/** + * The request policy rule for the canister change operation. + */ +export interface ExternalCanisterChangeRequestPolicyRule { + /** + * The request policy rule for the canister change operation. + */ + 'rule' : RequestPolicyRule, + /** + * The id of the request policy rule. + */ + 'policy_id' : UUID, +} +/** + * The request policy rule for the canister change operation. + */ +export interface ExternalCanisterChangeRequestPolicyRuleInput { + /** + * The request policy rule for the canister change operation. + */ + 'rule' : RequestPolicyRule, + /** + * The id of the request policy rule. + * + * If not provided a new entry will be created. + */ + 'policy_id' : [] | [UUID], +} +/** + * The target canister to interact with. + */ +export type ExternalCanisterId = { 'Any' : null } | + { 'Canister' : Principal }; +/** + * ExternalCanister can have additional information attached to them, + * this type can be used to represent the additional info. + */ +export interface ExternalCanisterMetadata { + /** + * The key of the additional info (e.g. "app_id") + */ + 'key' : string, + /** + * The value of the additional info (e.g. "2ec270f1-7663-4d51-b70f-9339486b6d6d") + */ + 'value' : string, +} +/** + * The permissions set for the external canister. + */ +export interface ExternalCanisterPermissions { + /** + * The permissions for the calling methods on the canister. + */ + 'calls' : Array, + /** + * Who can read information about the canister (e.g. canister status), + * changes to this permission can be made by the `change` permission. + */ + 'read' : Allow, + /** + * Who can make changes to the canister, includes: + * - changing the permissions + * - install operations + */ + 'change' : Allow, +} +/** + * The create input type for setting the permissions for the external canister. + */ +export type ExternalCanisterPermissionsCreateInput = ExternalCanisterPermissions; +/** + * The input type for setting the permissions for the external canister. + */ +export interface ExternalCanisterPermissionsUpdateInput { + /** + * The permissions for calling methods on the canister. + */ + 'calls' : [] | [ExternalCanisterChangeCallPermissionsInput], + /** + * Who can read information about the canister (e.g. canister status), + * changes to this permission can be made by the `change` permission. + */ + 'read' : [] | [Allow], + /** + * Who can make changes to the canister, includes: + * - changing the permissions + * - install operations + */ + 'change' : [] | [Allow], +} +/** + * The request policy rules for the external canister. + */ +export interface ExternalCanisterRequestPolicies { + /** + * The request policy rules for the calling methods on the canister. + */ + 'calls' : Array, + /** + * The request policy rules for the canister change operation. + */ + 'change' : Array, +} +/** + * The input type for setting the request policies for a new external canister. + */ +export interface ExternalCanisterRequestPoliciesCreateInput { + /** + * The request policy rules for the calling methods on the canister. + */ + 'calls' : Array, + /** + * The request policy rules for the canister change operation. + */ + 'change' : Array, +} +/** + * The input type for setting the request policies for an existing external canister. + */ +export interface ExternalCanisterRequestPoliciesUpdateInput { + /** + * The request policy rules for the calling methods on the canister. + */ + 'calls' : [] | [ExternalCanisterChangeCallRequestPoliciesInput], + /** + * The request policy rules for the canister change operation. + */ + 'change' : [] | [Array], +} +/** + * The actions that are available for external canisters. + */ +export type ExternalCanisterResourceAction = { + 'Call' : CallExternalCanisterResourceTarget + } | + { 'Fund' : ExternalCanisterId } | + { 'List' : null } | + { 'Read' : ExternalCanisterId } | + { 'Create' : null } | + { 'Change' : ExternalCanisterId }; +/** + * The state of the external canister. + */ +export type ExternalCanisterState = { + /** + * The record is active and can be interacted with. + */ + 'Active' : null + } | + { + /** + * The record is archived and can no longer be interacted with. + */ + 'Archived' : null + }; +/** + * Input type for getting a account balance. + */ +export interface FetchAccountBalancesInput { + /** + * The account ids to retrieve. + */ + 'account_ids' : Array, +} +/** + * Result type for getting a account. + */ +export type FetchAccountBalancesResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The account balance that was retrieved. + */ + 'balances' : Array<[] | [AccountBalance]>, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * The request operation for funding an external canister from the station. + */ +export type FundExternalCanisterOperation = FundExternalCanisterOperationInput; +/** + * The input type for funding an external canister in the station. + */ +export interface FundExternalCanisterOperationInput { + /** + * The kind of funding operation to perform. + */ + 'kind' : FundExternalCanisterOperationKind, + /** + * The external canister to fund. + */ + 'canister_id' : Principal, +} +/** + * The operation kind for funding an external canister in the station. + */ +export type FundExternalCanisterOperationKind = { + /** + * The amount of cycles to send to the canister. + */ + 'Send' : FundExternalCanisterSendCyclesInput + }; +/** + * The input type for specifying the cycles to send to an external canister. + */ +export interface FundExternalCanisterSendCyclesInput { + /** + * The amount of cycles to send to the canister. + */ + 'cycles' : bigint, +} +/** + * Input type for getting a account. + */ +export interface GetAccountInput { + /** + * The account id to retrieve. + */ + 'account_id' : UUID, +} +/** + * Result type for getting a account. + */ +export type GetAccountResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The privileges of the caller for the account. + */ + 'privileges' : AccountCallerPrivileges, + /** + * The account that was retrieved. + */ + 'account' : Account, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Input type for getting a single address book entry. + */ +export interface GetAddressBookEntryInput { + /** + * The address book entry id to retrieve. + */ + 'address_book_entry_id' : UUID, +} +/** + * Result type for getting an address book entry. + */ +export type GetAddressBookEntryResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The privileges of the caller for the address book entry. + */ + 'privileges' : AddressBookEntryCallerPrivileges, + /** + * The address book entry that was retrieved. + */ + 'address_book_entry' : AddressBookEntry, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * The input type for getting an asset. + */ +export interface GetAssetInput { + /** + * The asset id to retrieve. + */ + 'asset_id' : UUID, +} +/** + * The result type for getting an asset. + */ +export type GetAssetResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The caller privileges for the asset. + */ + 'privileges' : AssetCallerPrivileges, + /** + * The asset that was retrieved. + */ + 'asset' : Asset, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * The input type for getting the available filters for the external canisters. + */ +export interface GetExternalCanisterFiltersInput { + /** + * Include the labels of the external canisters in the result. + */ + 'with_labels' : [] | [boolean], + /** + * Include the names of the external canisters in the result. + */ + 'with_name' : [] | [ + { + /** + * The prefix to use for filtering the names. + * + * If the prefix is not provided, any name will be returned. + */ + 'prefix' : [] | [string], + } + ], +} +/** + * The result type for the filtering of external canisters. + */ +export type GetExternalCanisterFiltersResult = { + 'Ok' : { + /** + * The list of labels that are used by the external canisters. + */ + 'labels' : [] | [Array], + /** + * The list of names that are used by the external canisters + * and their canister id. + */ + 'names' : [] | [Array<{ 'name' : string, 'canister_id' : Principal }>], + } + } | + { 'Err' : Error }; +/** + * Input type for getting a external canister. + */ +export interface GetExternalCanisterInput { + /** + * The principal id of the external canister. + */ + 'canister_id' : Principal, +} +export type GetExternalCanisterResult = { + 'Ok' : { + /** + * The caller privileges for the external canister. + */ + 'privileges' : ExternalCanisterCallerPrivileges, + /** + * The external canister that was retrieved. + */ + 'canister' : ExternalCanister, + } + } | + { 'Err' : Error }; +/** + * Input type for getting a named rule. + */ +export interface GetNamedRuleInput { + /** + * The named rule to retrieve by the id. + */ + 'named_rule_id' : UUID, +} +/** + * Result type for retrieving a named rule. + */ +export type GetNamedRuleResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The privileges of the caller for the named rule. + */ + 'privileges' : NamedRuleCallerPrivileges, + /** + * The named rule that was retrieved. + */ + 'named_rule' : NamedRule, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * The input type for getting the list of requests based on the given filters. + */ +export interface GetNextApprovableRequestInput { + /** + * Get the next request from a list sorted by the given field. + */ + 'sort_by' : [] | [ListRequestsSortBy], + /** + * Exclude requests the user indicated to skip. + */ + 'excluded_request_ids' : Array, + /** + * The type of the request (e.g. "transfer"). + */ + 'operation_types' : [] | [Array], +} +/** + * Result type for retrieving a request. + */ +export type GetNextApprovableRequestResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : [] | [GetRequestResultData] + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +export interface GetPermissionInput { + /** + * The resource to retrieve the permission for. + */ + 'resource' : Resource, +} +export type GetPermissionResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The permission that was retrieved. + */ + 'permission' : Permission, + /** + * The privileges of the caller for the permission. + */ + 'privileges' : PermissionCallerPrivileges, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Input type for getting a request. + */ +export interface GetRequestInput { + /** + * The request id to retrieve. + */ + 'request_id' : UUID, + /** + * Fill in all the additional info about the request operation, request types such as `CallExternalCanisterOperation` + * will include the request argument, this can be a large amount of data and could potentially exceed the response + * size limit. + * + * If not provided, this field defaults to `false` and the additional info is not included in the response. + */ + 'with_full_info' : [] | [boolean], +} +export interface GetRequestPolicyInput { + /** + * The id of the request policy to retrieve. + */ + 'id' : UUID, +} +export type GetRequestPolicyResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The privileges of the caller for the request policy. + */ + 'privileges' : RequestPolicyCallerPrivileges, + /** + * The request policy that was retrieved. + */ + 'policy' : RequestPolicy, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Result type for retrieving a request. + */ +export type GetRequestResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : GetRequestResultData + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +export interface GetRequestResultData { + /** + * The privileges of the caller. + */ + 'privileges' : RequestCallerPrivileges, + /** + * The request that was requested. + */ + 'request' : Request, + /** + * The additional info about the request. + */ + 'additional_info' : RequestAdditionalInfo, +} +export interface GetTransfersInput { + /** + * The list of transfer ids to retrieve. + */ + 'transfer_ids' : Array, +} +export type GetTransfersResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The transfer that was retrieved. + */ + 'transfers' : Array, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Input type for getting a user group. + */ +export interface GetUserGroupInput { + /** + * The group id to retrieve. + */ + 'user_group_id' : UUID, +} +/** + * Result type for getting a user group. + */ +export type GetUserGroupResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The caller privileges for the user group. + */ + 'privileges' : UserGroupCallerPrivileges, + /** + * The group that was retrieved. + */ + 'user_group' : UserGroup, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Input type for retrieving a user. + */ +export interface GetUserInput { + /** + * The user id to retrieve (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + */ + 'user_id' : UUID, +} +/** + * Result type for retrieving a user. + */ +export type GetUserResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The caller privileges for the user. + */ + 'privileges' : UserCallerPrivileges, + /** + * The user that was retrieved. + */ + 'user' : User, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +export type HeaderField = [string, string]; +export type HealthStatus = { 'Healthy' : null } | + { 'Uninitialized' : null }; +export interface HttpRequest { + 'url' : string, + 'method' : string, + 'body' : Uint8Array | number[], + 'headers' : Array, +} +export interface HttpResponse { + 'body' : Uint8Array | number[], + 'headers' : Array, + 'status_code' : number, +} +/** + * The initial accounts to create when initializing the canister for the first time, e.g., after disaster recovery. + */ +export interface InitAccountInput { + /** + * The UUID of the account, if not provided a new UUID will be generated. + */ + 'id' : [] | [UUID], + /** + * Metadata associated with the account (e.g. `{"contract": "0x1234", "symbol": "ANY"}`). + */ + 'metadata' : Array, + /** + * A friendly name for the account (e.g. "My Account"). + */ + 'name' : string, + /** + * The asset standard for this account (e.g. `native`, `erc20`, etc.). + */ + 'assets' : Array, + /** + * The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + */ + 'seed' : AccountSeed, +} +/** + * The permissions for the account. + */ +export interface InitAccountPermissionsInput { + /** + * The approval policy for updates to the account. + */ + 'configs_request_policy' : [] | [RequestPolicyRule], + /** + * Who can read the account information. + */ + 'read_permission' : Allow, + /** + * Who can request updates to the account. + */ + 'configs_permission' : Allow, + /** + * The approval policy for transfers from the account. + */ + 'transfer_request_policy' : [] | [RequestPolicyRule], + /** + * Who can request transfers from the account. + */ + 'transfer_permission' : Allow, +} +/** + * The initial account to create when initializing the canister for the first time. + */ +export interface InitAccountWithPermissionsInput { + /** + * The permissions for the account. + */ + 'permissions' : InitAccountPermissionsInput, + /** + * The initial account to create. + */ + 'account_init' : InitAccountInput, +} +/** + * The initial assets to create when initializing the canister for the first time, e.g., after disaster recovery. + */ +export interface InitAssetInput { + /** + * The UUID of the asset, if not provided a new UUID will be generated. + */ + 'id' : [] | [UUID], + /** + * The number of decimals used to format the asset balance. + */ + 'decimals' : number, + /** + * The standards this asset supports. + */ + 'standards' : Array, + /** + * Metadata associated with the asset. + */ + 'metadata' : Array, + /** + * The name of the asset. + */ + 'name' : string, + /** + * The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + */ + 'blockchain' : string, + /** + * The asset symbol, e.g. "ICP" or "BTC". + */ + 'symbol' : string, +} +/** + * The init type for adding a named rule when initializing the canister for the first time. + */ +export interface InitNamedRuleInput { + /** + * The id of the named rule, if not provided a new UUID will be generated. + */ + 'id' : [] | [UUID], + /** + * The name of the named rule. + */ + 'name' : string, + /** + * The rule to use for the named rule. + */ + 'rule' : RequestPolicyRule, + /** + * The description of the named rule. + */ + 'description' : [] | [string], +} +/** + * The init type for initializing the permissions when first creating the canister. + */ +export interface InitPermissionInput { + /** + * The resource that the permission is for. + */ + 'resource' : Resource, + /** + * The allow rules for who can access the resource. + */ + 'allow' : Allow, +} +/** + * The init type for adding a request approval policy when initializing the canister for the first time. + */ +export interface InitRequestPolicyInput { + /** + * The id of the request policy, if not provided a new UUID will be generated. + */ + 'id' : [] | [UUID], + /** + * The rule to use for the request approval evaluation (e.g. "quorum"). + */ + 'rule' : RequestPolicyRule, + /** + * The request specifier that identifies what operation this policy is for (e.g. "transfer"). + */ + 'specifier' : RequestSpecifier, +} +/** + * The input type for creating a user group when initializing the canister for the first time. + */ +export interface InitUserGroupInput { + /** + * The id of the user group, if not provided a new UUID will be generated. + */ + 'id' : [] | [UUID], + /** + * The name of the user group, must be unique. + */ + 'name' : string, +} +/** + * The users to create when initializing the canister for the first time. + */ +export interface InitUserInput { + /** + * The id of the user, if not provided a new UUID will be generated. + */ + 'id' : [] | [UUID], + /** + * The status of the user (e.g. `Active`). + */ + 'status' : UserStatus, + /** + * The user groups to associate with the user (optional). + * If not provided it defaults to the [`Admin`,`Operator`] groups if default user groups are created, + * i.e., when the field `initial_config` in `SystemInit` has the form of `WithAllDefaults` or `WithDefaultPolicies`. + */ + 'groups' : [] | [Array], + /** + * The name of the user. + */ + 'name' : string, + /** + * The identities of the user. + */ + 'identities' : Array, +} +/** + * The initial configuration for the station. + * + * Unless the `Complete` variant is used, the station will be initialized with default user + * groups, named rules (aka. approval rules), request policies, permissions, and assets. + * + * The default user groups for the station will be: + * - `Admin` with the UUID "00000000-0000-4000-8000-000000000000" + * - `Operator` with the UUID "00000000-0000-4000-8000-000000000001" + * + * The default named rules for the station will be: + * - `Admin approval` with a specified admin quorum + * - `Operator approval` with a specified operator and admin quorum + * + */ +export type InitialConfig = { + /** + * Initialize the station with default user groups, named rules, policies, permissions. + */ + 'WithDefaultPolicies' : { + /** + * The initial assets to create. + */ + 'assets' : Array, + /** + * The initial admin quorum in the admin level approval rule. + */ + 'admin_quorum' : number, + /** + * The initial accounts to create. + */ + 'accounts' : Array, + /** + * The initial users to create. + */ + 'users' : Array, + /** + * The initial operator quorum in the operator level approval rule. + */ + 'operator_quorum' : number, + } + } | + { + /** + * Initialize the station with default user groups, named rules, policies, permissions, and assets. + * This does not create an initial account. + */ + 'WithAllDefaults' : { + /** + * The initial admin quorum in the admin level approval rule. + */ + 'admin_quorum' : number, + /** + * The initial users to create. + */ + 'users' : Array, + /** + * The initial operator quorum in the operator level approval rule. + */ + 'operator_quorum' : number, + } + } | + { + /** + * Initialize the station with all custom entries. + */ + 'Complete' : { + /** + * The initial permissions to create. + */ + 'permissions' : Array, + /** + * The initial assets to create. + */ + 'assets' : Array, + /** + * The initial request policies to create. + */ + 'request_policies' : Array, + /** + * The initial user groups to create. + */ + 'user_groups' : Array, + /** + * The initial accounts to create. + */ + 'accounts' : Array, + /** + * The initial disaster recovery committee to create. + */ + 'disaster_recovery_committee' : [] | [DisasterRecoveryCommittee], + /** + * The initial users to create. + */ + 'users' : Array, + /** + * The initial named rules to create. + */ + 'named_rules' : Array, + } + }; +export interface ListAccountTransfersInput { + /** + * The account id to retrieve. + */ + 'account_id' : UUID, + /** + * The transfer status in text format (e.g. "pending", "approved", etc.). + */ + 'status' : [] | [TransferStatusType], + /** + * Until which date to retrieve the transfers. + */ + 'to_dt' : [] | [TimestampRFC3339], + /** + * From which date to retrieve the transfers. + */ + 'from_dt' : [] | [TimestampRFC3339], +} +export type ListAccountTransfersResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The list of transfers. + */ + 'transfers' : Array, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * A record type that can be used search for accounts. + */ +export interface ListAccountsInput { + /** + * The pagination parameters. + */ + 'paginate' : [] | [PaginationInput], + /** + * The name of the account to search for. + */ + 'search_term' : [] | [string], +} +/** + * Result type for listing all accounts. + */ +export type ListAccountsResult = { + 'Ok' : { + /** + * The total number of users. + */ + 'total' : bigint, + /** + * The privileges of the caller. + */ + 'privileges' : Array, + /** + * The list of accounts. + */ + 'accounts' : Array, + /** + * The offset to use for the next page. + */ + 'next_offset' : [] | [bigint], + } + } | + { 'Err' : Error }; +/** + * Input type for listing address book entries for a given blockchain standard. + */ +export interface ListAddressBookEntriesInput { + /** + * The address boo entry ids to retrieve. + */ + 'ids' : [] | [Array], + /** + * The address formats to search for. + */ + 'address_formats' : [] | [Array], + /** + * The labels to search for, if provided only address book entries with the given labels will be returned. + */ + 'labels' : [] | [Array], + /** + * The blockchain identifier (e.g., `ethereum`, `bitcoin`, `icp`, etc.) + */ + 'blockchain' : [] | [string], + /** + * The address to search for. + */ + 'addresses' : [] | [Array], + /** + * The pagination parameters. + */ + 'paginate' : [] | [PaginationInput], + /** + * The term to use for filtering the address book entries. + */ + 'search_term' : [] | [string], +} +/** + * Result type for listing address book entries for a given blockchain standard. + */ +export type ListAddressBookEntriesResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The total number of address book entries for the given blockchain standard. + */ + 'total' : bigint, + /** + * The privileges of the caller for the address book entries. + */ + 'privileges' : Array, + /** + * The list of retrieved address book entries. + */ + 'address_book_entries' : Array, + /** + * The offset to use for the next page. + */ + 'next_offset' : [] | [bigint], + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * The input type for listing assets. + */ +export interface ListAssetsInput { + /** + * The pagination parameters. + */ + 'paginate' : [] | [PaginationInput], +} +/** + * The result type for listing assets. + */ +export type ListAssetsResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The total number of assets. + */ + 'total' : bigint, + /** + * The caller privileges for the assets. + */ + 'privileges' : Array, + /** + * The list of assets. + */ + 'assets' : Array, + /** + * The offset to use for the next page. + */ + 'next_offset' : [] | [bigint], + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Input type for listing external canisters with the given filters. + */ +export interface ListExternalCanistersInput { + /** + * The sort parameters. + */ + 'sort_by' : [] | [ListExternalCanistersSortInput], + /** + * The current state of the external canisters to use for filtering (e.g. `Active`, `Archived`). + */ + 'states' : [] | [Array], + /** + * The principal id of the external canister to search for. + */ + 'canister_ids' : [] | [Array], + /** + * The labels to use for filtering the external canisters. + */ + 'labels' : [] | [Array], + /** + * The pagination parameters. + */ + 'paginate' : [] | [PaginationInput], +} +/** + * Result type for listing external canisters. + */ +export type ListExternalCanistersResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The total number of external canisters. + */ + 'total' : bigint, + /** + * The caller privileges for the external canisters. + */ + 'privileges' : Array, + /** + * The list of external canisters. + */ + 'canisters' : Array, + /** + * The offset to use for the next page. + */ + 'next_offset' : [] | [bigint], + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * The input type for sorting the results of listing external canisters. + */ +export type ListExternalCanistersSortInput = { + /** + * Sort by the name of the external canister. + */ + 'Name' : SortByDirection + }; +/** + * Input type for listing named rules. + */ +export interface ListNamedRulesInput { + /** + * The pagination parameters. + */ + 'paginate' : [] | [PaginationInput], +} +/** + * Result type for listing named rules. + */ +export type ListNamedRulesResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The total number of named rules. + */ + 'total' : bigint, + /** + * The privileges of the caller. + */ + 'privileges' : Array, + /** + * The list of named rules. + */ + 'named_rules' : Array, + /** + * The offset to use for the next page. + */ + 'next_offset' : [] | [bigint], + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * The input type for getting the list of notifications associated with the caller. + */ +export interface ListNotificationsInput { + /** + * Show only notifications with the given status. + */ + 'status' : [] | [NotificationStatus], + /** + * Until which created time to retrieve the notifications. + */ + 'to_dt' : [] | [TimestampRFC3339], + /** + * From which created time to retrieve the notifications. + */ + 'from_dt' : [] | [TimestampRFC3339], + /** + * The type of the notification (e.g. "system-message"). + */ + 'notification_type' : [] | [NotificationTypeInput], +} +/** + * The result type for getting the list of notifications. + */ +export type ListNotificationsResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The list of notifications ordered by creation time (newest first). + */ + 'notifications' : Array, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Input type for listing permissions with the given pagination parameters. + */ +export interface ListPermissionsInput { + /** + * The resources to retrieve the permissions for. + */ + 'resources' : [] | [Array], + /** + * The pagination parameters. + */ + 'paginate' : [] | [PaginationInput], +} +/** + * Result type for listing permissions. + */ +export type ListPermissionsResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The list of permissions. + */ + 'permissions' : Array, + /** + * The total number of permissions. + */ + 'total' : bigint, + /** + * The caller privileges for the permissions. + */ + 'privileges' : Array, + /** + * The user groups that are associated with returned permissions. + */ + 'user_groups' : Array, + /** + * The users that are associated with returned permissions. + */ + 'users' : Array, + /** + * The offset to use for the next page. + */ + 'next_offset' : [] | [bigint], + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * Input type for listing request policies with the given pagination parameters. + */ +export type ListRequestPoliciesInput = PaginationInput; +/** + * Result type for listing request policies. + */ +export type ListRequestPoliciesResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The total number of request policies. + */ + 'total' : bigint, + /** + * The caller privileges for the request policies. + */ + 'privileges' : Array, + /** + * The offset to use for the next page. + */ + 'next_offset' : [] | [bigint], + /** + * The list of request policies. + */ + 'policies' : Array, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * The input type for getting the list of requests based on the given filters. + */ +export interface ListRequestsInput { + /** + * The sorting parameters. + */ + 'sort_by' : [] | [ListRequestsSortBy], + /** + * Return only requests with one of these deduplication keys. + */ + 'deduplication_keys' : [] | [Array], + /** + * Return the full evaluation results for the requests. + */ + 'with_evaluation_results' : boolean, + /** + * From which expiration time to retrieve the requests. + */ + 'expiration_from_dt' : [] | [TimestampRFC3339], + /** + * The tags to search. Return only requests which have at least one matching tag. + */ + 'tags' : [] | [Array], + /** + * Until which created time to retrieve the requests. + */ + 'created_to_dt' : [] | [TimestampRFC3339], + /** + * Show only requests with the given status. + */ + 'statuses' : [] | [Array], + /** + * Show only requests that the specified users have submitted an approval decision for. + */ + 'approver_ids' : [] | [Array], + /** + * Until which expiration time to retrieve the requests. + */ + 'expiration_to_dt' : [] | [TimestampRFC3339], + /** + * The pagination parameters. + */ + 'paginate' : [] | [PaginationInput], + /** + * Show only requests made by the given users. + */ + 'requester_ids' : [] | [Array], + /** + * The type of the request (e.g. "transfer"). + */ + 'operation_types' : [] | [Array], + /** + * Return only requests the the user can submit an approval decision for. + */ + 'only_approvable' : boolean, + /** + * From which created time to retrieve the requests. + */ + 'created_from_dt' : [] | [TimestampRFC3339], +} +export type ListRequestsOperationType = { + /** + * An operation for removing an asset. + */ + 'RemoveAsset' : null + } | + { + /** + * An operation for adding a new user group. + */ + 'AddUserGroup' : null + } | + { + /** + * An operation for editing an permission. + */ + 'EditPermission' : null + } | + { + /** + * An operation for snapshotting an external canister. + */ + 'SnapshotExternalCanister' : [] | [Principal] + } | + { + /** + * An operation for pruning an external canister. + */ + 'PruneExternalCanister' : [] | [Principal] + } | + { + /** + * An operation for editing a named rule. + */ + 'EditNamedRule' : null + } | + { + /** + * An operation for configuring an external canister. + */ + 'ConfigureExternalCanister' : [] | [Principal] + } | + { + /** + * An operation for changing a external canister with an optionally specified canister ID. + */ + 'ChangeExternalCanister' : [] | [Principal] + } | + { + /** + * An operation for monitoring cycles of an external canister. + */ + 'MonitorExternalCanister' : [] | [Principal] + } | + { + /** + * An operation for adding a new user. + */ + 'AddUser' : null + } | + { + /** + * An operation for editing an asset. + */ + 'EditAsset' : null + } | + { + /** + * An operation for editing an existing user group. + */ + 'EditUserGroup' : null + } | + { + /** + * An operation for setting disaster recovery config. + */ + 'SetDisasterRecovery' : null + } | + { + /** + * An operation for editing a request policy. + */ + 'EditRequestPolicy' : null + } | + { + /** + * An operation for removing a request policy. + */ + 'RemoveRequestPolicy' : null + } | + { + /** + * An operation for adding an asset. + */ + 'AddAsset' : null + } | + { + /** + * An operation for performing a system upgrade on the station or upgrader. + */ + 'SystemUpgrade' : null + } | + { + /** + * An operation for removing an address book entry. + */ + 'RemoveAddressBookEntry' : null + } | + { + /** + * An operation for restoring the station or upgrader from a snapshot. + */ + 'SystemRestore' : null + } | + { + /** + * An operation for creating a external canister. + */ + 'CreateExternalCanister' : null + } | + { + /** + * An operation for updating an address book entry. + */ + 'EditAddressBookEntry' : null + } | + { + /** + * An operation for sending cycles to an external canister. + */ + 'FundExternalCanister' : [] | [Principal] + } | + { + /** + * An operation for editing an existing user. + */ + 'EditUser' : null + } | + { + /** + * An operation for managing system info. + */ + 'ManageSystemInfo' : null + } | + { + /** + * A new transfer of funds from a given account. + */ + 'Transfer' : [] | [UUID] + } | + { + /** + * An operation for updating information of an account. + */ + 'EditAccount' : null + } | + { + /** + * An operation for creating a new address book entry. + */ + 'AddAddressBookEntry' : null + } | + { + /** + * An operation for adding a request policy. + */ + 'AddRequestPolicy' : null + } | + { + /** + * An operation for removing a named rule. + */ + 'RemoveNamedRule' : null + } | + { + /** + * An operation for removing an existing user group. + */ + 'RemoveUserGroup' : null + } | + { + /** + * An operation for calling an external canister with an optionally specified canister ID. + */ + 'CallExternalCanister' : [] | [Principal] + } | + { + /** + * An operation for adding a named rule. + */ + 'AddNamedRule' : null + } | + { + /** + * An operation for restoring an external canister from a snapshot. + */ + 'RestoreExternalCanister' : [] | [Principal] + } | + { + /** + * An operation for creating a new account. + */ + 'AddAccount' : null + }; +/** + * The result type for getting the list of requests. + */ +export type ListRequestsResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The total number of requests. + */ + 'total' : bigint, + /** + * The privileges of the caller. + */ + 'privileges' : Array, + /** + * The list of requests. + */ + 'requests' : Array, + /** + * The next offset to use for pagination. + */ + 'next_offset' : [] | [bigint], + /** + * The additional info about the requests. + */ + 'additional_info' : Array, + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +/** + * The input type that can be used to sort the list of requests by a given field. + */ +export type ListRequestsSortBy = { + /** + * Sort by the request expiration time. + */ + 'ExpirationDt' : SortByDirection + } | + { + /** + * Sort by the request last modification time. + */ + 'LastModificationDt' : SortByDirection + } | + { + /** + * Sort by the request creation time. + */ + 'CreatedAt' : SortByDirection + }; +export interface ListUserGroupsInput { + /** + * The pagination parameters. + */ + 'paginate' : [] | [PaginationInput], + /** + * The term to use for filtering the user groups. + */ + 'search_term' : [] | [string], +} +/** + * Result type for listing all user groups. + */ +export type ListUserGroupsResult = { + 'Ok' : { + /** + * The total number of user groups. + */ + 'total' : bigint, + /** + * The caller privileges for the user groups. + */ + 'privileges' : Array, + /** + * The list of groups. + */ + 'user_groups' : Array, + /** + * The offset to use for the next page. + */ + 'next_offset' : [] | [bigint], + } + } | + { 'Err' : Error }; +/** + * Input type for listing users of the station. + */ +export interface ListUsersInput { + /** + * The groups to use for filtering the users. + */ + 'groups' : [] | [Array], + /** + * The statuses to use for filtering the users. + */ + 'statuses' : [] | [Array], + /** + * The pagination parameters. + */ + 'paginate' : [] | [PaginationInput], + /** + * The search term to use for filtering the users. + */ + 'search_term' : [] | [string], +} +/** + * Result type for listing users of the station. + */ +export type ListUsersResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The total number of users. + */ + 'total' : bigint, + /** + * The privileges of the caller. + */ + 'privileges' : Array, + /** + * The list of users. + */ + 'users' : Array, + /** + * The offset to use for the next page. + */ + 'next_offset' : [] | [bigint], + } + } | + { + /** + * The error that occurred (e.g. the user does not have the necessary permissions). + */ + 'Err' : Error + }; +export type LogVisibility = { 'controllers' : null } | + { 'public' : null } | + { 'allowed_viewers' : Array }; +/** + * An operation for managing the system information. + */ +export interface ManageSystemInfoOperation { + /** + * The input to the request to manage the system information. + */ + 'input' : ManageSystemInfoOperationInput, +} +/** + * Input type for managing the system information. + */ +export interface ManageSystemInfoOperationInput { + /** + * The name of the station. + */ + 'name' : [] | [string], + /** + * The strategy to use to for the station to top itself up with cycles. + */ + 'cycle_obtain_strategy' : [] | [CycleObtainStrategyInput], + /** + * The maximum number of upgrader backup snapshots to keep. + */ + 'max_upgrader_backup_snapshots' : [] | [bigint], + /** + * The maximum number of station backup snapshots to keep. + */ + 'max_station_backup_snapshots' : [] | [bigint], +} +export type MarkNotificationReadResult = { 'Ok' : null } | + { 'Err' : Error }; +export interface MarkNotificationsReadInput { + /** + * The notifications to mark as read. + */ + 'notification_ids' : Array, + /** + * If true, all notifications will be marked as read. + */ + 'read' : boolean, +} +export type MeResult = { + 'Ok' : { + /** + * The user that is associated with the caller. + */ + 'me' : User, + /** + * The list of privileges associated with the user. + */ + 'privileges' : Array, + } + } | + { 'Err' : Error }; +/** + * The request operation for monitoring an external canister from the station. + */ +export type MonitorExternalCanisterOperation = MonitorExternalCanisterOperationInput; +/** + * The input type for monitoring an external canister in the station. + */ +export interface MonitorExternalCanisterOperationInput { + /** + * The kind of funding operation to perform. + */ + 'kind' : MonitorExternalCanisterOperationKind, + /** + * The external canister to monitor. + */ + 'canister_id' : Principal, +} +/** + * The operation kind for monitoring an external canister in the station. + */ +export type MonitorExternalCanisterOperationKind = { + 'Start' : MonitorExternalCanisterStartInput + } | + { 'Stop' : null }; +/** + * The input type for specifying the strategy for monitoring an external canister. + */ +export interface MonitorExternalCanisterStartInput { + /** + * The strategy for obtaining cycles for the funding operation. + */ + 'cycle_obtain_strategy' : [] | [CycleObtainStrategyInput], + /** + * The strategy for funding the canister. + */ + 'funding_strategy' : MonitorExternalCanisterStrategyInput, +} +/** + * The input type for specifying the strategy for monitoring an external canister. + */ +export type MonitorExternalCanisterStrategyInput = { + /** + * Fund the canister at a fixed interval with the specified amount of cycles. + */ + 'Always' : bigint + } | + { + /** + * Fund the canister when the balance is below the threshold. + */ + 'BelowThreshold' : MonitoringExternalCanisterCyclesThresholdInput + } | + { + /** + * Fund the canister based on the estimated run time in seconds. + */ + 'BelowEstimatedRuntime' : MonitoringExternalCanisterEstimatedRuntimeInput + }; +export interface MonitoringExternalCanisterCyclesThresholdInput { + /** + * / The cycles to fund the canister with when the threshold is triggered. + */ + 'fund_cycles' : bigint, + /** + * / The min cycles threshold to trigger the funding operation. + */ + 'min_cycles' : bigint, +} +export interface MonitoringExternalCanisterEstimatedRuntimeInput { + /** + * / The runtime seconds to add to the estimated runtime. + */ + 'fund_runtime_secs' : bigint, + /** + * / The fallback min cycles to trigger the funding operation when the estimated runtime is not available, + * / or the cycles balance is below the threshold. + */ + 'fallback_min_cycles' : bigint, + /** + * / The estimated min runtime in seconds to trigger the funding operation. + */ + 'min_runtime_secs' : bigint, + /** + * / The fallback cycles to fund the canister with when the estimated runtime is not available, + * / or the cycles balance is below the threshold. + */ + 'fallback_fund_cycles' : bigint, + /** + * / The maximum cycles to fund the canister with, only used when the estimated runtime is available. + */ + 'max_runtime_cycles_fund' : bigint, +} +/** + * The named rule type. + * + * A named rule is a reusable configuration that can be applied to many approval policies. + */ +export interface NamedRule { + /** + * The rule id. + */ + 'id' : UUID, + /** + * The rule name. + */ + 'name' : string, + /** + * The rule value. + */ + 'rule' : RequestPolicyRule, + /** + * The rule description. + */ + 'description' : [] | [string], +} +/** + * A record type that can be used to represent the caller privileges for a given named rule. + */ +export interface NamedRuleCallerPrivileges { + /** + * The named rule id. + */ + 'id' : UUID, + /** + * Whether or not the caller can delete the resource. + */ + 'can_delete' : boolean, + /** + * Whether or not the caller can edit the resource. + */ + 'can_edit' : boolean, +} +/** + * The blockchain network to used in a transaction. + */ +export interface Network { + /** + * The network id, represented by the blockchain symbol and network name (e.g. "icp:mainnet"). + */ + 'id' : NetworkId, + /** + * The name of the network (e.g. "Mainnet"). + */ + 'name' : string, +} +/** + * The network id, represented by the blockchain symbol and network name (e.g. "icp:mainnet"). + */ +export type NetworkId = string; +/** + * A record type that can be used to represent a notification. + */ +export interface Notification { + /** + * The notification id which is a UUID (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + */ + 'id' : UUID, + /** + * The notification status. + */ + 'status' : NotificationStatus, + /** + * The notification title in a single locale. + */ + 'title' : string, + /** + * The time at which the notification was created. + */ + 'created_at' : TimestampRFC3339, + /** + * The type of the notification. + */ + 'notification_type' : NotificationType, + /** + * The notification message in a single locale. + */ + 'message' : [] | [string], + /** + * The user that the notification is for. + */ + 'target_user_id' : UUID, +} +/** + * The actions that are available for notifications. + */ +export type NotificationResourceAction = { 'List' : null } | + { 'Update' : ResourceId }; +/** + * Defines the various states that a notification can be in. + */ +export type NotificationStatus = { + /** + * The notification has been read by the user. + */ + 'Read' : null + } | + { + /** + * The notification has been sent. + */ + 'Sent' : null + }; +/** + * Represents the different types of notifications within the system. + */ +export type NotificationType = { + /** + * Notification for the creation of a new request. + * This should be used to alert users when a new request that requires their attention has been created. + */ + 'RequestCreated' : { + /** + * Account id is available for relevant request types. + */ + 'account_id' : [] | [UUID], + /** + * The request id that was created. + */ + 'request_id' : UUID, + /** + * The type of the request (e.g. "transfer"). + */ + 'operation_type' : RequestOperationType, + /** + * User id is available for relevant request types. + */ + 'user_id' : [] | [UUID], + } + } | + { + /** + * Notification for the rejection of a request. + * This should be used to alert the requester when a request has been rejected. + */ + 'RequestRejected' : { + /** + * The request id that was created. + */ + 'request_id' : UUID, + /** + * List of reasons why the request was rejected. + */ + 'reasons' : [] | [Array], + /** + * The type of the request (e.g. "transfer"). + */ + 'operation_type' : RequestOperationType, + } + } | + { + /** + * Notification for system-wide messages. + * This can be used for announcements, scheduled maintenance reminders, or other important system messages. + */ + 'SystemMessage' : null + } | + { + /** + * Notification for the failure of a request. + * This should be used to alert the requester when a request has failed to be executed. + */ + 'RequestFailed' : { + /** + * The request id that was created. + */ + 'request_id' : UUID, + /** + * The type of the request (e.g. "transfer"). + */ + 'operation_type' : RequestOperationType, + /** + * Details about the failure. + */ + 'reason' : [] | [string], + } + }; +export type NotificationTypeInput = { 'RequestCreated' : null } | + { 'SystemMessage' : null }; +export interface NotifyFailedStationUpgradeInput { + /** + * the failure reason + */ + 'reason' : string, +} +export type NotifyFailedStationUpgradeResult = { 'Ok' : null } | + { 'Err' : Error }; +export interface PaginationInput { + /** + * The offset to use for pagination. + */ + 'offset' : [] | [bigint], + /** + * The maximum number of items to retrieve. + * + * If not set, the default limit will be used. + */ + 'limit' : [] | [number], +} +/** + * The permission, used to specify the rules for users when interacting with resources. + */ +export interface Permission { + /** + * The resource that the permission is for. + */ + 'resource' : Resource, + /** + * The allowed users and user groups for the resource. + */ + 'allow' : Allow, +} +/** + * A record type that can be used to represent the caller privileges for a given permission. + */ +export interface PermissionCallerPrivileges { + /** + * The resource that the caller has privileges for. + */ + 'resource' : Resource, + /** + * Whether or not the caller can edit the resource. + */ + 'can_edit' : boolean, +} +/** + * The actions that are available for permissions. + */ +export type PermissionResourceAction = { 'Read' : null } | + { 'Update' : null }; +export interface PruneExternalCanisterOperation { + 'input' : PruneExternalCanisterOperationInput, +} +export interface PruneExternalCanisterOperationInput { + /** + * The canister to prune. + */ + 'canister_id' : Principal, + /** + * The resource to prune. + */ + 'prune' : { 'snapshot' : string } | + { 'state' : null } | + { 'chunk_store' : null }, +} +/** + * A record type that can be used to represent the minimum quorum of users that are required to approve a rule. + */ +export interface Quorum { + /** + * The minimum number of user approvals required for the rule to be approved. + */ + 'min_approved' : number, + /** + * The users that can approve the request. + */ + 'approvers' : UserSpecifier, +} +/** + * A record type that can be used to represent a percentage of users that are required to approve a rule. + */ +export interface QuorumPercentage { + /** + * The required user approvals for the rule to be approved. + */ + 'min_approved' : number, + /** + * The users that are required to approve the request. + */ + 'approvers' : UserSpecifier, +} +export interface RemoveAddressBookEntryOperation { + /** + * The input to the request to remove the address book entry. + */ + 'input' : RemoveAddressBookEntryOperationInput, +} +/** + * Input type for removing an address book entry through a request. + */ +export interface RemoveAddressBookEntryOperationInput { + /** + * The id of the address book entry. + */ + 'address_book_entry_id' : UUID, +} +export interface RemoveAssetOperation { + /** + * The input to the request to remove an asset. + */ + 'input' : RemoveAssetOperationInput, +} +/** + * The input type for removing an asset. + */ +export interface RemoveAssetOperationInput { + /** + * The asset id to remove. + */ + 'asset_id' : UUID, +} +/** + * The operation type for removing a named rule. + */ +export interface RemoveNamedRuleOperation { + /** + * The input to the request to remove a named rule. + */ + 'input' : RemoveNamedRuleOperationInput, +} +/** + * The input type for deleting a named rule. + */ +export interface RemoveNamedRuleOperationInput { + /** + * The named rule id to remove. + */ + 'named_rule_id' : UUID, +} +export interface RemoveRequestPolicyOperation { + /** + * The input to the request to remove a request policy. + */ + 'input' : RemoveRequestPolicyOperationInput, +} +export interface RemoveRequestPolicyOperationInput { + /** + * The request policy id that will be removed. + */ + 'policy_id' : UUID, +} +export interface RemoveUserGroupOperation { + /** + * The input to the request to remove the user group. + */ + 'input' : RemoveUserGroupOperationInput, +} +export interface RemoveUserGroupOperationInput { + /** + * The id of the group to remove. + */ + 'user_group_id' : UUID, +} +/** + * A record type that can be used to represent a requested operation in the station. + */ +export interface Request { + /** + * The request id which is a UUID (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + */ + 'id' : UUID, + /** + * The request approval status. + */ + 'status' : RequestStatus, + /** + * The request title. + */ + 'title' : string, + /** + * The time at which the request should be executed if approved. + */ + 'execution_plan' : RequestExecutionSchedule, + /** + * The time at which the request will expire. + */ + 'expiration_dt' : TimestampRFC3339, + /** + * The optional deduplication key used to ensure request uniqueness. + */ + 'deduplication_key' : [] | [string], + /** + * The tags that were provided during request creation. + */ + 'tags' : Array, + /** + * The time at which the request was created. + */ + 'created_at' : TimestampRFC3339, + /** + * The user that created the request. + */ + 'requested_by' : UUID, + /** + * The request summary (e.g. "This request will transfer 100 ICP to the account 0x1234"). + */ + 'summary' : [] | [string], + /** + * The operation that was requested. + */ + 'operation' : RequestOperation, + /** + * The list of user approvals on the request. + */ + 'approvals' : Array, +} +/** + * A record type that can be used to represent additional information about a request. + */ +export interface RequestAdditionalInfo { + /** + * The request id. + */ + 'id' : UUID, + /** + * The evaluation result of all matching policies for the request. + */ + 'evaluation_result' : [] | [RequestEvaluationResult], + /** + * The requester name (e.g. "John Doe"). + */ + 'requester_name' : string, + /** + * Display information for the approvers. + */ + 'approvers' : Array, +} +/** + * A record type that is used to represent a user approval decision on a request. + */ +export interface RequestApproval { + /** + * The user has added to the request, once provided it cannot be changed. + */ + 'status' : RequestApprovalStatus, + /** + * The user that has recorded the approval decision. + */ + 'approver_id' : UUID, + /** + * Optional reason for the decision. + */ + 'status_reason' : [] | [string], + /** + * The time at which the decision was made. + */ + 'decided_at' : TimestampRFC3339, +} +/** + * The status of a request. + */ +export type RequestApprovalStatus = { 'Approved' : null } | + { 'Rejected' : null }; +/** + * A record type that can be used to represent the caller privileges for a given request. + */ +export interface RequestCallerPrivileges { + /** + * The request id. + */ + 'id' : UUID, + /** + * Whether or not the caller can submit an approval decision. + */ + 'can_approve' : boolean, +} +/** + * A record type representing the full evaluation result of all matching policies for a request. + */ +export interface RequestEvaluationResult { + /** + * The request id that was evaluated. + */ + 'request_id' : UUID, + /** + * The final evaluation status of the request. + */ + 'status' : EvaluationStatus, + /** + * The reasons why the request was approved or rejected. + */ + 'result_reasons' : [] | [Array], + /** + * The evaluation results of all matching policies. + */ + 'policy_results' : Array, +} +/** + * The schedule for executing a transaction of a given transfer. + */ +export type RequestExecutionSchedule = { + /** + * The transaction will be executed immediately. + */ + 'Immediate' : null + } | + { + /** + * The transaction will be executed at a given time. + */ + 'Scheduled' : { + /** + * The time at which the transaction will be executed, + * it must be in the future. + */ + 'execution_time' : TimestampRFC3339, + } + }; +export type RequestOperation = { + /** + * An operation for removing an existing asset. + */ + 'RemoveAsset' : RemoveAssetOperation + } | + { + /** + * An operation for adding a new user group. + */ + 'AddUserGroup' : AddUserGroupOperation + } | + { + /** + * An operation for editing an permission. + */ + 'EditPermission' : EditPermissionOperation + } | + { + /** + * An operation for snapshotting an external canister. + */ + 'SnapshotExternalCanister' : SnapshotExternalCanisterOperation + } | + { + /** + * An operation for pruning an external canister. + */ + 'PruneExternalCanister' : PruneExternalCanisterOperation + } | + { + /** + * An operation for editing an existing named rule. + */ + 'EditNamedRule' : EditNamedRuleOperation + } | + { + /** + * An operation for configuring an external canister. + */ + 'ConfigureExternalCanister' : ConfigureExternalCanisterOperation + } | + { + /** + * An operation for changing a external canister. + */ + 'ChangeExternalCanister' : ChangeExternalCanisterOperation + } | + { + /** + * An operation for monitoring an external canister. + */ + 'MonitorExternalCanister' : MonitorExternalCanisterOperation + } | + { + /** + * An operation for adding a new user. + */ + 'AddUser' : AddUserOperation + } | + { + /** + * An operation for editing an existing asset. + */ + 'EditAsset' : EditAssetOperation + } | + { + /** + * An operation for editing an existing user group. + */ + 'EditUserGroup' : EditUserGroupOperation + } | + { + /** + * An operation for setting disaster recovery. + */ + 'SetDisasterRecovery' : SetDisasterRecoveryOperation + } | + { + /** + * An operation for editing a request policy. + */ + 'EditRequestPolicy' : EditRequestPolicyOperation + } | + { + /** + * An operation for removing a request policy. + */ + 'RemoveRequestPolicy' : RemoveRequestPolicyOperation + } | + { + /** + * An operation for adding a new asset. + */ + 'AddAsset' : AddAssetOperation + } | + { + /** + * An operation for performing a system upgrade on the station or upgrader. + */ + 'SystemUpgrade' : SystemUpgradeOperation + } | + { + /** + * An operation for removing an existing address book entry. + */ + 'RemoveAddressBookEntry' : RemoveAddressBookEntryOperation + } | + { + /** + * An operation for restoring the station or upgrader from a snapshot. + */ + 'SystemRestore' : SystemRestoreOperation + } | + { + /** + * An operation for creating a external canister. + */ + 'CreateExternalCanister' : CreateExternalCanisterOperation + } | + { + /** + * An operation for updating an existing address book entry. + */ + 'EditAddressBookEntry' : EditAddressBookEntryOperation + } | + { + /** + * An operation for funding an external canister. + */ + 'FundExternalCanister' : FundExternalCanisterOperation + } | + { + /** + * An operation for editing an existing user. + */ + 'EditUser' : EditUserOperation + } | + { + /** + * An operation for managing system info. + */ + 'ManageSystemInfo' : ManageSystemInfoOperation + } | + { + /** + * A new transfer of funds from a given account. + */ + 'Transfer' : TransferOperation + } | + { + /** + * An operation for updating information of an account. + */ + 'EditAccount' : EditAccountOperation + } | + { + /** + * An operation for creating a new address book entry. + */ + 'AddAddressBookEntry' : AddAddressBookEntryOperation + } | + { + /** + * An operation for adding a request policy. + */ + 'AddRequestPolicy' : AddRequestPolicyOperation + } | + { + /** + * An operation for removing an existing named rule. + */ + 'RemoveNamedRule' : RemoveNamedRuleOperation + } | + { + /** + * An operation for removing an existing user group. + */ + 'RemoveUserGroup' : RemoveUserGroupOperation + } | + { + /** + * An operation for calling an external canister. + */ + 'CallExternalCanister' : CallExternalCanisterOperation + } | + { + /** + * An operation for adding a new named rule. + */ + 'AddNamedRule' : AddNamedRuleOperation + } | + { + /** + * An operation for restoring an external canister from a snapshot. + */ + 'RestoreExternalCanister' : RestoreExternalCanisterOperation + } | + { + /** + * An operation for creating a new account. + */ + 'AddAccount' : AddAccountOperation + }; +export type RequestOperationInput = { + /** + * An operation for removing an existing asset. + */ + 'RemoveAsset' : RemoveAssetOperationInput + } | + { + /** + * An operation for adding a new user group. + */ + 'AddUserGroup' : AddUserGroupOperationInput + } | + { + /** + * An operation for editing an permission. + */ + 'EditPermission' : EditPermissionOperationInput + } | + { + /** + * An operation for snapshotting an external canister. + */ + 'SnapshotExternalCanister' : SnapshotExternalCanisterOperationInput + } | + { + /** + * An operation for pruning an external canister. + */ + 'PruneExternalCanister' : PruneExternalCanisterOperationInput + } | + { + /** + * An operation for editing an existing named rule. + */ + 'EditNamedRule' : EditNamedRuleOperationInput + } | + { + /** + * An operation for configuring an external canister. + */ + 'ConfigureExternalCanister' : ConfigureExternalCanisterOperationInput + } | + { + /** + * An operation for changing a external canister. + */ + 'ChangeExternalCanister' : ChangeExternalCanisterOperationInput + } | + { + /** + * An operation for monitoring an external canister. + */ + 'MonitorExternalCanister' : MonitorExternalCanisterOperationInput + } | + { + /** + * An operation for adding a new user. + */ + 'AddUser' : AddUserOperationInput + } | + { + /** + * An operation for editing an existing asset. + */ + 'EditAsset' : EditAssetOperationInput + } | + { + /** + * An operation for editing an existing user group. + */ + 'EditUserGroup' : EditUserGroupOperationInput + } | + { + /** + * An operation for setting disaster recovery. + */ + 'SetDisasterRecovery' : SetDisasterRecoveryOperationInput + } | + { + /** + * An operation for editing a request policy. + */ + 'EditRequestPolicy' : EditRequestPolicyOperationInput + } | + { + /** + * An operation for removing a request policy. + */ + 'RemoveRequestPolicy' : RemoveRequestPolicyOperationInput + } | + { + /** + * An operation for adding a new asset. + */ + 'AddAsset' : AddAssetOperationInput + } | + { + /** + * An operation for performing a system upgrade on the station or upgrader. + */ + 'SystemUpgrade' : SystemUpgradeOperationInput + } | + { + /** + * An operation for removing an address book entry. + */ + 'RemoveAddressBookEntry' : RemoveAddressBookEntryOperationInput + } | + { + /** + * An operation for restoring the station or upgrader from a snapshot. + */ + 'SystemRestore' : SystemRestoreOperationInput + } | + { + /** + * An operation for creating a external canister. + */ + 'CreateExternalCanister' : CreateExternalCanisterOperationInput + } | + { + /** + * An operation for updating an address book entry. + */ + 'EditAddressBookEntry' : EditAddressBookEntryOperationInput + } | + { + /** + * An operation for funding an external canister. + */ + 'FundExternalCanister' : FundExternalCanisterOperationInput + } | + { + /** + * An operation for editing an existing user. + */ + 'EditUser' : EditUserOperationInput + } | + { + /** + * An operation for managing system info. + */ + 'ManageSystemInfo' : ManageSystemInfoOperationInput + } | + { + /** + * A new transfer of funds from a given account. + */ + 'Transfer' : TransferOperationInput + } | + { + /** + * An operation for updating information of an account. + */ + 'EditAccount' : EditAccountOperationInput + } | + { + /** + * An operation for creating a new address book entry. + */ + 'AddAddressBookEntry' : AddAddressBookEntryOperationInput + } | + { + /** + * An operation for adding a request policy. + */ + 'AddRequestPolicy' : AddRequestPolicyOperationInput + } | + { + /** + * An operation for removing an existing named rule. + */ + 'RemoveNamedRule' : RemoveNamedRuleOperationInput + } | + { + /** + * An operation for removing an existing user group. + */ + 'RemoveUserGroup' : RemoveUserGroupOperationInput + } | + { + /** + * An operation for calling an external canister. + */ + 'CallExternalCanister' : CallExternalCanisterOperationInput + } | + { + /** + * An operation for adding a new named rule. + */ + 'AddNamedRule' : AddNamedRuleOperationInput + } | + { + /** + * An operation for restoring an external canister from a snapshot. + */ + 'RestoreExternalCanister' : RestoreExternalCanisterOperationInput + } | + { + /** + * An operation for adding a new account. + */ + 'AddAccount' : AddAccountOperationInput + }; +export type RequestOperationType = { + /** + * An operation for removing an existing asset. + */ + 'RemoveAsset' : null + } | + { + /** + * An operation for adding a new user group. + */ + 'AddUserGroup' : null + } | + { + /** + * An operation for editing an permission. + */ + 'EditPermission' : null + } | + { + /** + * An operation for snapshotting an external canister. + */ + 'SnapshotExternalCanister' : null + } | + { + /** + * An operation for pruning an external canister. + */ + 'PruneExternalCanister' : null + } | + { + /** + * An operation for editing an existing named rule. + */ + 'EditNamedRule' : null + } | + { + /** + * An operation for creating a external canister. + */ + 'ConfigureExternalCanister' : null + } | + { + /** + * An operation for changing a external canister. + */ + 'ChangeExternalCanister' : null + } | + { + /** + * An operation for monitoring cycles of an external canister. + */ + 'MonitorExternalCanister' : null + } | + { + /** + * An operation for adding a new user. + */ + 'AddUser' : null + } | + { + /** + * An operation for editing an existing asset. + */ + 'EditAsset' : null + } | + { + /** + * An operation for editing an existing user group. + */ + 'EditUserGroup' : null + } | + { + /** + * An operation for setting disaster recovery for a canister. + */ + 'SetDisasterRecovery' : null + } | + { + /** + * An operation for editing a request policy. + */ + 'EditRequestPolicy' : null + } | + { + /** + * An operation for removing a request policy. + */ + 'RemoveRequestPolicy' : null + } | + { + /** + * An operation for adding a new asset. + */ + 'AddAsset' : null + } | + { + /** + * An operation for performing a system upgrade on the station or upgrader. + */ + 'SystemUpgrade' : null + } | + { + /** + * An operation for removing an address book entry. + */ + 'RemoveAddressBookEntry' : null + } | + { + /** + * An operation for restoring the station or upgrader from a snapshot. + */ + 'SystemRestore' : null + } | + { + /** + * An operation for creating a external canister. + */ + 'CreateExternalCanister' : null + } | + { + /** + * An operation for updating an address book entry. + */ + 'EditAddressBookEntry' : null + } | + { + /** + * An operation for sending cycles to an external canister. + */ + 'FundExternalCanister' : null + } | + { + /** + * An operation for editing an existing user. + */ + 'EditUser' : null + } | + { + /** + * And operation for managing system info. + */ + 'ManageSystemInfo' : null + } | + { + /** + * A new transfer of funds from a given account. + */ + 'Transfer' : null + } | + { + /** + * An operation for updating information of an account. + */ + 'EditAccount' : null + } | + { + /** + * An operation for creating a new address book entry. + */ + 'AddAddressBookEntry' : null + } | + { + /** + * An operation for adding a request policy. + */ + 'AddRequestPolicy' : null + } | + { + /** + * An operation for removing an existing named rule. + */ + 'RemoveNamedRule' : null + } | + { + /** + * An operation for removing an existing user group. + */ + 'RemoveUserGroup' : null + } | + { + /** + * An operation for calling an external canister. + */ + 'CallExternalCanister' : null + } | + { + /** + * An operation for adding a new named rule. + */ + 'AddNamedRule' : null + } | + { + /** + * An operation for restoring an external canister from a snapshot. + */ + 'RestoreExternalCanister' : null + } | + { + /** + * An operation for creating a new account. + */ + 'AddAccount' : null + }; +/** + * Represents a request policy with the associated rule. + */ +export interface RequestPolicy { + 'id' : UUID, + 'rule' : RequestPolicyRule, + 'specifier' : RequestSpecifier, +} +/** + * A record type that can be used to represent the caller privileges for a given request policy. + */ +export interface RequestPolicyCallerPrivileges { + /** + * The request policy id. + */ + 'id' : UUID, + /** + * Whether or not the caller can delete the resource. + */ + 'can_delete' : boolean, + /** + * Whether or not the caller can edit the resource. + */ + 'can_edit' : boolean, +} +/** + * Defines the various types rules that can be used in a request evaluation. + */ +export type RequestPolicyRule = { 'Not' : RequestPolicyRule } | + { 'Quorum' : Quorum } | + { 'AllowListed' : null } | + { 'QuorumPercentage' : QuorumPercentage } | + { 'AutoApproved' : null } | + { 'AllOf' : Array } | + { 'AnyOf' : Array } | + { 'AllowListedByMetadata' : AddressBookMetadata } | + { 'NamedRule' : UUID }; +export type RequestPolicyRuleInput = { 'Set' : RequestPolicyRule } | + { 'Remove' : null }; +/** + * A record type representing the full evaluation result of a request policy rule. + */ +export interface RequestPolicyRuleResult { + /** + * The final evaluation status of the rule. + */ + 'status' : EvaluationStatus, + /** + * The result of the evaluation of the rule and all its sub-rules. + */ + 'evaluated_rule' : EvaluatedRequestPolicyRule, +} +/** + * The actions that are available for requests. + */ +export type RequestResourceAction = { 'List' : null } | + { 'Read' : ResourceId }; +/** + * Defines the various types of requests that can be created. + */ +export type RequestSpecifier = { 'RemoveAsset' : ResourceIds } | + { 'AddUserGroup' : null } | + { 'EditPermission' : ResourceSpecifier } | + { 'EditNamedRule' : ResourceIds } | + { 'ChangeExternalCanister' : ExternalCanisterId } | + { 'AddUser' : null } | + { 'EditAsset' : ResourceIds } | + { 'EditUserGroup' : ResourceIds } | + { 'SetDisasterRecovery' : null } | + { 'EditRequestPolicy' : ResourceIds } | + { 'RemoveRequestPolicy' : ResourceIds } | + { 'AddAsset' : null } | + { 'SystemUpgrade' : null } | + { 'RemoveAddressBookEntry' : ResourceIds } | + { 'CreateExternalCanister' : null } | + { 'EditAddressBookEntry' : ResourceIds } | + { 'FundExternalCanister' : ExternalCanisterId } | + { 'EditUser' : ResourceIds } | + { 'ManageSystemInfo' : null } | + { 'Transfer' : ResourceIds } | + { 'EditAccount' : ResourceIds } | + { 'AddAddressBookEntry' : null } | + { 'AddRequestPolicy' : null } | + { 'RemoveNamedRule' : ResourceIds } | + { 'RemoveUserGroup' : ResourceIds } | + { 'CallExternalCanister' : CallExternalCanisterResourceTarget } | + { 'AddNamedRule' : null } | + { 'AddAccount' : null }; +/** + * The status of a request. + */ +export type RequestStatus = { 'Failed' : { 'reason' : [] | [string] } } | + { 'Approved' : null } | + { 'Rejected' : null } | + { 'Scheduled' : { 'scheduled_at' : TimestampRFC3339 } } | + { 'Cancelled' : { 'reason' : [] | [string] } } | + { 'Processing' : { 'started_at' : TimestampRFC3339 } } | + { 'Created' : null } | + { 'Completed' : { 'completed_at' : TimestampRFC3339 } }; +/** + * The status code of a request. + */ +export type RequestStatusCode = { 'Failed' : null } | + { 'Approved' : null } | + { 'Rejected' : null } | + { 'Scheduled' : null } | + { 'Cancelled' : null } | + { 'Processing' : null } | + { 'Created' : null } | + { 'Completed' : null }; +/** + * The Resource is used to specify what is being accessed. + */ +export type Resource = { 'Request' : RequestResourceAction } | + { 'Notification' : NotificationResourceAction } | + { 'System' : SystemResourceAction } | + { 'User' : UserResourceAction } | + { 'ExternalCanister' : ExternalCanisterResourceAction } | + { 'Account' : AccountResourceAction } | + { 'AddressBook' : ResourceAction } | + { 'Asset' : ResourceAction } | + { 'NamedRule' : ResourceAction } | + { 'UserGroup' : ResourceAction } | + { 'Permission' : PermissionResourceAction } | + { 'RequestPolicy' : ResourceAction }; +/** + * The resource actions, used to specify the action that is performed on a resource. + */ +export type ResourceAction = { 'List' : null } | + { 'Read' : ResourceId } | + { 'Delete' : ResourceId } | + { 'Create' : null } | + { 'Update' : ResourceId }; +/** + * The record id of a resource, used to specify the resource that is being accessed. + */ +export type ResourceId = { 'Id' : UUID } | + { 'Any' : null }; +/** + * The record ids of a resource, used to specify the resources that are being accessed. + */ +export type ResourceIds = { 'Any' : null } | + { 'Ids' : Array }; +export type ResourceSpecifier = { 'Any' : null } | + { 'Resource' : Resource }; +export interface RestoreExternalCanisterOperation { + 'input' : RestoreExternalCanisterOperationInput, +} +export interface RestoreExternalCanisterOperationInput { + /** + * The canister to restore from a snapshot. + */ + 'canister_id' : Principal, + /** + * A snapshot to be restored. + */ + 'snapshot_id' : string, +} +export interface SetDisasterRecoveryOperation { + /** + * The disaster recovery committee. + */ + 'committee' : [] | [DisasterRecoveryCommittee], +} +export interface SetDisasterRecoveryOperationInput { + /** + * The disaster recovery committee. + */ + 'committee' : [] | [DisasterRecoveryCommittee], +} +/** + * The hash string representation for sha256. + */ +export type Sha256Hash = string; +export interface SnapshotExternalCanisterOperation { + 'input' : SnapshotExternalCanisterOperationInput, + /** + * The snapshot id of the new snapshot. + */ + 'snapshot_id' : [] | [string], +} +export interface SnapshotExternalCanisterOperationInput { + /** + * Should a snapshot be taken if the external canister fails to stop. + */ + 'force' : boolean, + /** + * A snapshot to be replaced. + */ + 'replace_snapshot' : [] | [string], + /** + * The canister to snapshot. + */ + 'canister_id' : Principal, +} +/** + * The direction to use for sorting. + */ +export type SortByDirection = { + /** + * Sort in ascending order. + */ + 'Asc' : null + } | + { + /** + * Sort in descending order. + */ + 'Desc' : null + }; +/** + * Describes a standard suported by a blockchain. + */ +export interface StandardData { + /** + * Supported operations for the standard (e.g. `["transfer", "list_transfers", "balance"]`). + */ + 'supported_operations' : Array, + /** + * Supported address formats of the standard. + */ + 'supported_address_formats' : Array, + /** + * Required metadata fields for the standard (e.g. `["ledger_canister_id"]`). + */ + 'required_metadata_fields' : Array, + /** + * The standard name. + */ + 'standard' : string, +} +/** + * Input type for submitting an approval decision on a request. + */ +export interface SubmitRequestApprovalInput { + /** + * The request id to interact with. + */ + 'request_id' : UUID, + /** + * The decision to submit. + */ + 'decision' : RequestApprovalStatus, + /** + * The reason for the approval or rejection. + */ + 'reason' : [] | [string], +} +/** + * Result type for submitting an approval decision on a request. + */ +export type SubmitRequestApprovalResult = { + 'Ok' : { + /** + * The privileges of the caller. + */ + 'privileges' : RequestCallerPrivileges, + /** + * The request that the decision was submitted for. + */ + 'request' : Request, + /** + * The additional info about the request. + */ + 'additional_info' : RequestAdditionalInfo, + } + } | + { 'Err' : Error }; +export interface SubnetFilter { 'subnet_type' : [] | [string] } +export type SubnetSelection = { + /** + * Choose a random subnet that fulfills the specified properties + */ + 'Filter' : SubnetFilter + } | + { + /** + * Choose a specific subnet + */ + 'Subnet' : { 'subnet' : Principal } + }; +/** + * Describes a blockchain and its standards supported by the station. + */ +export interface SupportedBlockchain { + /** + * The blockchain name. + */ + 'blockchain' : string, + /** + * The supported standards for the blockchain. + */ + 'supported_standards' : Array, +} +/** + * The system information. + */ +export interface SystemInfo { + /** + * The disaster recovery configuration. + */ + 'disaster_recovery' : [] | [DisasterRecovery], + /** + * Cycle balance of the canister. + */ + 'upgrader_cycles' : [] | [bigint], + /** + * The name of the station. + */ + 'name' : string, + /** + * The time at which the canister was last upgraded. + */ + 'last_upgrade_timestamp' : TimestampRFC3339, + /** + * Did the canister successfully fetched randomness from the management canister. + */ + 'raw_rand_successful' : boolean, + /** + * The station version. + */ + 'version' : string, + /** + * Cycle balance of the station. + */ + 'cycles' : bigint, + /** + * The upgrader principal id. + */ + 'upgrader_id' : Principal, + /** + * Strategy defining how the station canister tops up its own cycles. + */ + 'cycle_obtain_strategy' : CycleObtainStrategy, + /** + * The maximum number of upgrader backup snapshots to keep. + */ + 'max_upgrader_backup_snapshots' : bigint, + /** + * The maximum number of station backup snapshots to keep. + */ + 'max_station_backup_snapshots' : bigint, +} +/** + * Result type for getting the canister system information. + */ +export type SystemInfoResult = { + /** + * The result data for a successful execution. + */ + 'Ok' : { + /** + * The system information. + */ + 'system' : SystemInfo, + } + } | + { + /** + * The error that occurred (e.g. the caller does not have sufficient privileges). + */ + 'Err' : Error + }; +export interface SystemInit { + /** + * The name of the station. + */ + 'name' : string, + /** + * The initial configuration to apply. + */ + 'initial_config' : InitialConfig, + /** + * An additional controller of the station and upgrader canisters (optional). + */ + 'fallback_controller' : [] | [Principal], + /** + * The upgrader configuration. + */ + 'upgrader' : SystemUpgraderInput, +} +/** + * The input type for the canister install method (e.g. init or upgrade). + */ +export type SystemInstall = { + /** + * The configuration to use when upgrading the canister. + */ + 'Upgrade' : SystemUpgrade + } | + { + /** + * The configuration to use when initializing the canister. + */ + 'Init' : SystemInit + }; +/** + * The actions that are available for the system. + */ +export type SystemResourceAction = { 'Upgrade' : null } | + { 'ManageSystemInfo' : null } | + { 'SystemInfo' : null } | + { 'Capabilities' : null }; +export interface SystemRestoreOperation { + 'input' : SystemRestoreOperationInput, +} +export interface SystemRestoreOperationInput { + /** + * The target to restore from a snapshot. + */ + 'target' : SystemRestoreTarget, + /** + * A snapshot to be restored. + */ + 'snapshot_id' : string, +} +export type SystemRestoreTarget = { 'RestoreUpgrader' : null } | + { 'RestoreStation' : null }; +/** + * The upgrade configuration for the canister. + */ +export interface SystemUpgrade { + /** + * The updated name of the station. + */ + 'name' : [] | [string], +} +export interface SystemUpgradeOperation { + /** + * Determines whether a backup snapshot should be taken (before the upgrade). + * If so and the maximum number of backup snapshots is reached, + * then the oldest backup snapshot is atomically replaced + * by the new backup snapshot. + */ + 'take_backup_snapshot' : [] | [boolean], + /** + * The checksum of the wasm module. + */ + 'module_checksum' : Sha256Hash, + /** + * The target to change. + */ + 'target' : SystemUpgradeTarget, + /** + * The checksum of the arg blob. + */ + 'arg_checksum' : [] | [Sha256Hash], +} +export interface SystemUpgradeOperationInput { + /** + * The initial argument passed to the new wasm module. + */ + 'arg' : [] | [Uint8Array | number[]], + /** + * Additional wasm module chunks to append to the wasm module. + */ + 'module_extra_chunks' : [] | [WasmModuleExtraChunks], + /** + * Determines whether a backup snapshot should be taken (before the upgrade). + * If so and the maximum number of backup snapshots is reached, + * then the oldest backup snapshot is atomically replaced + * by the new backup snapshot. + */ + 'take_backup_snapshot' : [] | [boolean], + /** + * The target to change. + */ + 'target' : SystemUpgradeTarget, + /** + * The wasm module to install. + */ + 'module' : Uint8Array | number[], +} +export type SystemUpgradeTarget = { 'UpgradeUpgrader' : null } | + { 'UpgradeStation' : null }; +/** + * An input type for configuring the upgrader canister. + */ +export type SystemUpgraderInput = { + /** + * An existing upgrader canister. + */ + 'Id' : Principal + } | + { + /** + * Creates and deploys a new canister. + */ + 'Deploy' : { + /** + * The initial cycles to allocate to the canister. + * + * If not set, only the minimal amount of cycles required to create + * and deploy the canister will be allocated. + */ + 'initial_cycles' : [] | [bigint], + 'wasm_module' : Uint8Array | number[], + } + }; +/** + * The timestamp type used in the canister. + */ +export type TimestampRFC3339 = string; +/** + * A record type that can be used to represent a transfer in a given account. + */ +export interface Transfer { + /** + * The internal transfer id, this a unique identifier for the transfer. + */ + 'id' : UUID, + /** + * The destination address of the transaction (e.g. "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"). + */ + 'to' : string, + /** + * The fee to pay for the transaction, if applicable. + */ + 'fee' : bigint, + /** + * The id of the request that created the transfer. + */ + 'request_id' : UUID, + /** + * The status of the transfer. + */ + 'status' : TransferStatus, + /** + * The account id to use for the transfer. + */ + 'from_account_id' : UUID, + /** + * Transfers can be tagged with optional additional info (e.g. a `nonce` for Ethereum transactions). + */ + 'metadata' : Array, + /** + * The network used when submitting the transaction to the blockchain. + */ + 'network' : Network, + /** + * The amount to transfer. + */ + 'amount' : bigint, +} +export interface TransferListItem { + /** + * The destination address of the transaction (e.g. "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"). + */ + 'to' : string, + /** + * The id of the request that created the transfer. + */ + 'request_id' : UUID, + /** + * The status of the transfer. + */ + 'status' : TransferStatus, + /** + * The time at which the transfer was created. + */ + 'created_at' : TimestampRFC3339, + /** + * The transfer id. + */ + 'transfer_id' : UUID, + /** + * The amount to transfer. + */ + 'amount' : bigint, +} +/** + * Transfers can have additional information attached to them, + * this type can be used to represent the additional info. + */ +export interface TransferMetadata { + /** + * The key of the additional info (e.g. "nonce", "tag", "memo", etc...) + */ + 'key' : string, + /** + * The value of the additional info (e.g. "0x1234" or "my-tag") + */ + 'value' : string, +} +/** + * Input type for transferring funds. + */ +export interface TransferOperation { + /** + * The fee paid for the transaction. + */ + 'fee' : [] | [bigint], + /** + * The asset to use for the transaction. + */ + 'from_asset' : Asset, + /** + * The network to use for the transaction. + */ + 'network' : Network, + /** + * The id of the executed transfer. + */ + 'transfer_id' : [] | [UUID], + /** + * The account to use for the transaction. + */ + 'from_account' : [] | [Account], + /** + * The input to the request to transfer funds. + */ + 'input' : TransferOperationInput, +} +/** + * Input type for transferring funds. + */ +export interface TransferOperationInput { + /** + * The destination address of the transaction (e.g. "1BvBMSE..."). + */ + 'to' : string, + /** + * The fee to pay for the transaction, if applicable. + * + * If not set, the default fee will be used. + */ + 'fee' : [] | [bigint], + /** + * The standard to use for the transfer. + */ + 'with_standard' : string, + /** + * The account id to use for the transaction. + */ + 'from_account_id' : UUID, + /** + * Transactions can be tagged with an optional additional info + * (e.g. a nonce in the case of an Ethereum transaction) + */ + 'metadata' : Array, + /** + * The network to use for the transaction, if not the + * default network of the account will be used. + */ + 'network' : [] | [Network], + /** + * The amount to transfer. + */ + 'amount' : bigint, + /** + * The asset id to transfer. + */ + 'from_asset_id' : UUID, +} +/** + * The status of a transfer. + */ +export type TransferStatus = { + /** + * The transfer has been failed. + */ + 'Failed' : { + /** + * The failure reason. + */ + 'reason' : string, + } + } | + { + /** + * The transfer is being processed. + */ + 'Processing' : { + /** + * The time at which the transfer started being processed. + */ + 'started_at' : TimestampRFC3339, + } + } | + { + /** + * The transfer is created for processing. + */ + 'Created' : null + } | + { + /** + * The transfer has been completed. + * + * For natively supported tokens this means that the transaction has + * submitted to the blockchain. For non natively supported tokens this means + * that the transaction has been signed and can be submitted by the client. + */ + 'Completed' : { + /** + * The base64 encoded value of the signed transaction, if available. + */ + 'signature' : [] | [string], + /** + * The transaction hash, if available. + */ + 'hash' : [] | [string], + /** + * Time at which the transaction was completed. + */ + 'completed_at' : TimestampRFC3339, + } + }; +/** + * Transfer status type for filtering on the transfer status. + */ +export type TransferStatusType = { 'Failed' : null } | + { 'Processing' : null } | + { 'Created' : null } | + { 'Completed' : null }; +/** + * Most ids under the station canister are in the UUID format (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + */ +export type UUID = string; +/** + * A record type that can be used to represent a user in the station. + */ +export interface User { + /** + * The UUID of the user (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + */ + 'id' : UUID, + /** + * The status of the user (e.g. `Active`). + */ + 'status' : UserStatus, + /** + * The list of groups the user belongs to. + * + * Users can be tagged with groups that can be used to control access to resources. + */ + 'groups' : Array, + /** + * The user name (e.g. "John Doe"). + */ + 'name' : string, + /** + * The time at which the user was created or last modified (e.g. "2021-01-01T00:00:00Z"). + */ + 'last_modification_timestamp' : TimestampRFC3339, + /** + * The principals associated with the user. + */ + 'identities' : Array, +} +/** + * A record type that can be used to represent the privileges of a caller for a given user. + */ +export interface UserCallerPrivileges { + /** + * The user id. + */ + 'id' : UUID, + /** + * Whether or not the caller can edit the user. + */ + 'can_edit' : boolean, +} +/** + * A record type that can be used to represent a user group in the station. + */ +export interface UserGroup { + /** + * The UUID of the group (e.g. "d0cf5b3f-7017-4cb8-9dcf-52619c42a7b0"). + */ + 'id' : UUID, + /** + * The name of the group (e.g. "Finance"). + */ + 'name' : string, +} +/** + * A record type that can be used to represent the privileges of a caller for a given user group. + */ +export interface UserGroupCallerPrivileges { + /** + * The user id. + */ + 'id' : UUID, + /** + * Whether or not the caller can delete the user group. + */ + 'can_delete' : boolean, + /** + * Whether or not the caller can edit the user group. + */ + 'can_edit' : boolean, +} +/** + * The input type for adding identities to a user. + */ +export interface UserIdentityInput { + /** + * The identity of the user. + */ + 'identity' : Principal, +} +/** + * The top level privileges that the user has when making calls to the canister. + */ +export type UserPrivilege = { 'AddUserGroup' : null } | + { 'ListRequestPolicies' : null } | + { 'ListNamedRules' : null } | + { 'ListPermissions' : null } | + { 'ListUserGroups' : null } | + { 'AddUser' : null } | + { 'ListUsers' : null } | + { 'AddAsset' : null } | + { 'SystemUpgrade' : null } | + { 'CreateExternalCanister' : null } | + { 'ListAssets' : null } | + { 'ManageSystemInfo' : null } | + { 'AddAddressBookEntry' : null } | + { 'ListAccounts' : null } | + { 'AddRequestPolicy' : null } | + { 'ListAddressBookEntries' : null } | + { 'ListExternalCanisters' : null } | + { 'ListRequests' : null } | + { 'CallAnyExternalCanister' : null } | + { 'SystemInfo' : null } | + { 'AddNamedRule' : null } | + { 'Capabilities' : null } | + { 'AddAccount' : null }; +/** + * The actions that are available for users. + */ +export type UserResourceAction = { 'List' : null } | + { 'Read' : ResourceId } | + { 'Create' : null } | + { 'Update' : ResourceId }; +/** + * Defines a user in the context of a request. + */ +export type UserSpecifier = { 'Id' : Array } | + { 'Any' : null } | + { 'Group' : Array }; +export type UserStatus = { + /** + * The user is inactive. + */ + 'Inactive' : null + } | + { + /** + * The user is active. + */ + 'Active' : null + }; +/** + * The validation method targets of a `CallExternalCanister` request. + */ +export type ValidationMethodResourceTarget = { 'No' : null } | + { 'ValidationMethod' : CanisterMethod }; +export interface WasmModuleExtraChunks { + /** + * The hash of the assembled wasm module. + */ + 'wasm_module_hash' : Uint8Array | number[], + /** + * The asset canister from which the chunks are to be retrieved. + */ + 'store_canister' : Principal, + /** + * The name of the asset containing extra chunks in the asset canister. + */ + 'extra_chunks_key' : string, +} +/** + * The Station service definition. + */ +export interface _SERVICE { + /** + * Cancel a request if the request is in a cancelable state. + * + * Cancelable conditions: + * + * - The request is in the `Created` state. + * - The caller is the requester of the request. + */ + 'cancel_request' : ActorMethod<[CancelRequestInput], CancelRequestResult>, + /** + * Get snapshots of a canister controlled by the station. + */ + 'canister_snapshots' : ActorMethod< + [CanisterSnapshotsInput], + CanisterSnapshotsResult + >, + /** + * Get canister status of a canister controlled by the station. + */ + 'canister_status' : ActorMethod< + [CanisterStatusInput], + CanisterStatusResponse + >, + /** + * This method exposes the supported assets and other capabilities of the canister. + * + * By default can be accessed by any active user. + */ + 'capabilities' : ActorMethod<[], CapabilitiesResult>, + /** + * Create a new request. + * + * The request will be created and the caller will be added as the requester. + */ + 'create_request' : ActorMethod<[CreateRequestInput], CreateRequestResult>, + /** + * Get the account balance. + * + * If the caller does not have access to the account, an error will be returned. + */ + 'fetch_account_balances' : ActorMethod< + [FetchAccountBalancesInput], + FetchAccountBalancesResult + >, + /** + * Get a account by id. + * + * If the caller does not have access to the account, an error will be returned. + */ + 'get_account' : ActorMethod<[GetAccountInput], GetAccountResult>, + /** + * If the caller does not have access to the address book entry, an error will be returned. + */ + 'get_address_book_entry' : ActorMethod< + [GetAddressBookEntryInput], + GetAddressBookEntryResult + >, + /** + * Get an asset by id. + */ + 'get_asset' : ActorMethod<[GetAssetInput], GetAssetResult>, + /** + * Get the external canister by its canister id. + */ + 'get_external_canister' : ActorMethod< + [GetExternalCanisterInput], + GetExternalCanisterResult + >, + /** + * Get the available filters for the external canisters. + */ + 'get_external_canister_filters' : ActorMethod< + [GetExternalCanisterFiltersInput], + GetExternalCanisterFiltersResult + >, + /** + * Get a named rule by id. + */ + 'get_named_rule' : ActorMethod<[GetNamedRuleInput], GetNamedRuleResult>, + /** + * Finds the next aprovable request for the caller. + */ + 'get_next_approvable_request' : ActorMethod< + [GetNextApprovableRequestInput], + GetNextApprovableRequestResult + >, + /** + * Get the permission for the resource provided. + */ + 'get_permission' : ActorMethod<[GetPermissionInput], GetPermissionResult>, + /** + * Get the request by id. + */ + 'get_request' : ActorMethod<[GetRequestInput], GetRequestResult>, + /** + * Get request policy by id. + */ + 'get_request_policy' : ActorMethod< + [GetRequestPolicyInput], + GetRequestPolicyResult + >, + /** + * Get transfers by their ids. + */ + 'get_transfers' : ActorMethod<[GetTransfersInput], GetTransfersResult>, + /** + * Get the user associated with the user id provided. + */ + 'get_user' : ActorMethod<[GetUserInput], GetUserResult>, + /** + * Get a user group by id. + * + * If the caller does not have access to the user group, an error will be returned. + */ + 'get_user_group' : ActorMethod<[GetUserGroupInput], GetUserGroupResult>, + /** + * Check if the station is healthy and ready to be used. + */ + 'health_status' : ActorMethod<[], HealthStatus>, + /** + * HTTP Protocol interface. + */ + 'http_request' : ActorMethod<[HttpRequest], HttpResponse>, + /** + * List all transfers from the requested account. + */ + 'list_account_transfers' : ActorMethod< + [ListAccountTransfersInput], + ListAccountTransfersResult + >, + /** + * List all accounts that the caller has access to. + * + * If the caller is not the owner of any account, an error will be returned. + */ + 'list_accounts' : ActorMethod<[ListAccountsInput], ListAccountsResult>, + /** + * List all address book entries for a given blockchain standard. + */ + 'list_address_book_entries' : ActorMethod< + [ListAddressBookEntriesInput], + ListAddressBookEntriesResult + >, + /** + * List all assets that the caller has access to. + */ + 'list_assets' : ActorMethod<[ListAssetsInput], ListAssetsResult>, + /** + * List all external canisters that the caller has access to. + */ + 'list_external_canisters' : ActorMethod< + [ListExternalCanistersInput], + ListExternalCanistersResult + >, + /** + * List named rules that the caller has access to. + */ + 'list_named_rules' : ActorMethod<[ListNamedRulesInput], ListNamedRulesResult>, + /** + * Get the list of notifications associated with the caller. + */ + 'list_notifications' : ActorMethod< + [ListNotificationsInput], + ListNotificationsResult + >, + /** + * List all permissions. + */ + 'list_permissions' : ActorMethod< + [ListPermissionsInput], + ListPermissionsResult + >, + /** + * List add request policies. + */ + 'list_request_policies' : ActorMethod< + [ListRequestPoliciesInput], + ListRequestPoliciesResult + >, + /** + * Get the list of requests. + * + * Only requests that the caller has access to will be returned. + */ + 'list_requests' : ActorMethod<[ListRequestsInput], ListRequestsResult>, + /** + * List all user groups of the station. + */ + 'list_user_groups' : ActorMethod<[ListUserGroupsInput], ListUserGroupsResult>, + /** + * List all users of the station. + */ + 'list_users' : ActorMethod<[ListUsersInput], ListUsersResult>, + /** + * Mark the notifications as read. + */ + 'mark_notifications_read' : ActorMethod< + [MarkNotificationsReadInput], + MarkNotificationReadResult + >, + /** + * Get the authenticated user and its privileges from the caller. + */ + 'me' : ActorMethod<[], MeResult>, + /** + * Internal endpoint used by the upgrader canister to notify the station about a failed station upgrade request. + */ + 'notify_failed_station_upgrade' : ActorMethod< + [NotifyFailedStationUpgradeInput], + NotifyFailedStationUpgradeResult + >, + /** + * Submits the user approval decision for a request. + */ + 'submit_request_approval' : ActorMethod< + [SubmitRequestApprovalInput], + SubmitRequestApprovalResult + >, + /** + * Get the system information of the canister (e.g. version, cycles, etc.). + * + * This method contains sensitive information and is up to the canister owner to + * decide who can access it (e.g. only admins). + */ + 'system_info' : ActorMethod<[], SystemInfoResult>, +} +export declare const idlFactory: IDL.InterfaceFactory; +export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/cli/src/audit/generated/station.did.js b/cli/src/audit/generated/station.did.js new file mode 100644 index 000000000..f76911c40 --- /dev/null +++ b/cli/src/audit/generated/station.did.js @@ -0,0 +1,2170 @@ +export const idlFactory = ({ IDL }) => { + const RequestPolicyRule = IDL.Rec(); + const RequestPolicyRuleResult = IDL.Rec(); + const SystemUpgrade = IDL.Record({ 'name' : IDL.Opt(IDL.Text) }); + const UUID = IDL.Text; + const AssetMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text }); + const InitAssetInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'decimals' : IDL.Nat32, + 'standards' : IDL.Vec(IDL.Text), + 'metadata' : IDL.Vec(AssetMetadata), + 'name' : IDL.Text, + 'blockchain' : IDL.Text, + 'symbol' : IDL.Text, + }); + const AccountMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text }); + const AccountSeed = IDL.Vec(IDL.Nat8); + const InitAccountInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'metadata' : IDL.Vec(AccountMetadata), + 'name' : IDL.Text, + 'assets' : IDL.Vec(UUID), + 'seed' : AccountSeed, + }); + const UserStatus = IDL.Variant({ + 'Inactive' : IDL.Null, + 'Active' : IDL.Null, + }); + const UserIdentityInput = IDL.Record({ 'identity' : IDL.Principal }); + const InitUserInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'status' : UserStatus, + 'groups' : IDL.Opt(IDL.Vec(UUID)), + 'name' : IDL.Text, + 'identities' : IDL.Vec(UserIdentityInput), + }); + const ResourceId = IDL.Variant({ 'Id' : UUID, 'Any' : IDL.Null }); + const RequestResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Read' : ResourceId, + }); + const NotificationResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Update' : ResourceId, + }); + const SystemResourceAction = IDL.Variant({ + 'Upgrade' : IDL.Null, + 'ManageSystemInfo' : IDL.Null, + 'SystemInfo' : IDL.Null, + 'Capabilities' : IDL.Null, + }); + const UserResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Read' : ResourceId, + 'Create' : IDL.Null, + 'Update' : ResourceId, + }); + const CanisterMethod = IDL.Record({ + 'canister_id' : IDL.Principal, + 'method_name' : IDL.Text, + }); + const ExecutionMethodResourceTarget = IDL.Variant({ + 'Any' : IDL.Null, + 'ExecutionMethod' : CanisterMethod, + }); + const ValidationMethodResourceTarget = IDL.Variant({ + 'No' : IDL.Null, + 'ValidationMethod' : CanisterMethod, + }); + const CallExternalCanisterResourceTarget = IDL.Record({ + 'execution_method' : ExecutionMethodResourceTarget, + 'validation_method' : ValidationMethodResourceTarget, + }); + const ExternalCanisterId = IDL.Variant({ + 'Any' : IDL.Null, + 'Canister' : IDL.Principal, + }); + const ExternalCanisterResourceAction = IDL.Variant({ + 'Call' : CallExternalCanisterResourceTarget, + 'Fund' : ExternalCanisterId, + 'List' : IDL.Null, + 'Read' : ExternalCanisterId, + 'Create' : IDL.Null, + 'Change' : ExternalCanisterId, + }); + const AccountResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Read' : ResourceId, + 'Create' : IDL.Null, + 'Transfer' : ResourceId, + 'Update' : ResourceId, + }); + const ResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Read' : ResourceId, + 'Delete' : ResourceId, + 'Create' : IDL.Null, + 'Update' : ResourceId, + }); + const PermissionResourceAction = IDL.Variant({ + 'Read' : IDL.Null, + 'Update' : IDL.Null, + }); + const Resource = IDL.Variant({ + 'Request' : RequestResourceAction, + 'Notification' : NotificationResourceAction, + 'System' : SystemResourceAction, + 'User' : UserResourceAction, + 'ExternalCanister' : ExternalCanisterResourceAction, + 'Account' : AccountResourceAction, + 'AddressBook' : ResourceAction, + 'Asset' : ResourceAction, + 'NamedRule' : ResourceAction, + 'UserGroup' : ResourceAction, + 'Permission' : PermissionResourceAction, + 'RequestPolicy' : ResourceAction, + }); + const AuthScope = IDL.Variant({ + 'Authenticated' : IDL.Null, + 'Public' : IDL.Null, + 'Restricted' : IDL.Null, + }); + const Allow = IDL.Record({ + 'user_groups' : IDL.Vec(UUID), + 'auth_scope' : AuthScope, + 'users' : IDL.Vec(UUID), + }); + const InitPermissionInput = IDL.Record({ + 'resource' : Resource, + 'allow' : Allow, + }); + const UserSpecifier = IDL.Variant({ + 'Id' : IDL.Vec(UUID), + 'Any' : IDL.Null, + 'Group' : IDL.Vec(UUID), + }); + const Quorum = IDL.Record({ + 'min_approved' : IDL.Nat16, + 'approvers' : UserSpecifier, + }); + const QuorumPercentage = IDL.Record({ + 'min_approved' : IDL.Nat16, + 'approvers' : UserSpecifier, + }); + const AddressBookMetadata = IDL.Record({ + 'key' : IDL.Text, + 'value' : IDL.Text, + }); + RequestPolicyRule.fill( + IDL.Variant({ + 'Not' : RequestPolicyRule, + 'Quorum' : Quorum, + 'AllowListed' : IDL.Null, + 'QuorumPercentage' : QuorumPercentage, + 'AutoApproved' : IDL.Null, + 'AllOf' : IDL.Vec(RequestPolicyRule), + 'AnyOf' : IDL.Vec(RequestPolicyRule), + 'AllowListedByMetadata' : AddressBookMetadata, + 'NamedRule' : UUID, + }) + ); + const ResourceIds = IDL.Variant({ 'Any' : IDL.Null, 'Ids' : IDL.Vec(UUID) }); + const ResourceSpecifier = IDL.Variant({ + 'Any' : IDL.Null, + 'Resource' : Resource, + }); + const RequestSpecifier = IDL.Variant({ + 'RemoveAsset' : ResourceIds, + 'AddUserGroup' : IDL.Null, + 'EditPermission' : ResourceSpecifier, + 'EditNamedRule' : ResourceIds, + 'ChangeExternalCanister' : ExternalCanisterId, + 'AddUser' : IDL.Null, + 'EditAsset' : ResourceIds, + 'EditUserGroup' : ResourceIds, + 'SetDisasterRecovery' : IDL.Null, + 'EditRequestPolicy' : ResourceIds, + 'RemoveRequestPolicy' : ResourceIds, + 'AddAsset' : IDL.Null, + 'SystemUpgrade' : IDL.Null, + 'RemoveAddressBookEntry' : ResourceIds, + 'CreateExternalCanister' : IDL.Null, + 'EditAddressBookEntry' : ResourceIds, + 'FundExternalCanister' : ExternalCanisterId, + 'EditUser' : ResourceIds, + 'ManageSystemInfo' : IDL.Null, + 'Transfer' : ResourceIds, + 'EditAccount' : ResourceIds, + 'AddAddressBookEntry' : IDL.Null, + 'AddRequestPolicy' : IDL.Null, + 'RemoveNamedRule' : ResourceIds, + 'RemoveUserGroup' : ResourceIds, + 'CallExternalCanister' : CallExternalCanisterResourceTarget, + 'AddNamedRule' : IDL.Null, + 'AddAccount' : IDL.Null, + }); + const InitRequestPolicyInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'rule' : RequestPolicyRule, + 'specifier' : RequestSpecifier, + }); + const InitUserGroupInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'name' : IDL.Text, + }); + const InitAccountPermissionsInput = IDL.Record({ + 'configs_request_policy' : IDL.Opt(RequestPolicyRule), + 'read_permission' : Allow, + 'configs_permission' : Allow, + 'transfer_request_policy' : IDL.Opt(RequestPolicyRule), + 'transfer_permission' : Allow, + }); + const InitAccountWithPermissionsInput = IDL.Record({ + 'permissions' : InitAccountPermissionsInput, + 'account_init' : InitAccountInput, + }); + const DisasterRecoveryCommittee = IDL.Record({ + 'user_group_id' : UUID, + 'quorum' : IDL.Nat16, + }); + const InitNamedRuleInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'name' : IDL.Text, + 'rule' : RequestPolicyRule, + 'description' : IDL.Opt(IDL.Text), + }); + const InitialConfig = IDL.Variant({ + 'WithDefaultPolicies' : IDL.Record({ + 'assets' : IDL.Vec(InitAssetInput), + 'admin_quorum' : IDL.Nat16, + 'accounts' : IDL.Vec(InitAccountInput), + 'users' : IDL.Vec(InitUserInput), + 'operator_quorum' : IDL.Nat16, + }), + 'WithAllDefaults' : IDL.Record({ + 'admin_quorum' : IDL.Nat16, + 'users' : IDL.Vec(InitUserInput), + 'operator_quorum' : IDL.Nat16, + }), + 'Complete' : IDL.Record({ + 'permissions' : IDL.Vec(InitPermissionInput), + 'assets' : IDL.Vec(InitAssetInput), + 'request_policies' : IDL.Vec(InitRequestPolicyInput), + 'user_groups' : IDL.Vec(InitUserGroupInput), + 'accounts' : IDL.Vec(InitAccountWithPermissionsInput), + 'disaster_recovery_committee' : IDL.Opt(DisasterRecoveryCommittee), + 'users' : IDL.Vec(InitUserInput), + 'named_rules' : IDL.Vec(InitNamedRuleInput), + }), + }); + const SystemUpgraderInput = IDL.Variant({ + 'Id' : IDL.Principal, + 'Deploy' : IDL.Record({ + 'initial_cycles' : IDL.Opt(IDL.Nat), + 'wasm_module' : IDL.Vec(IDL.Nat8), + }), + }); + const SystemInit = IDL.Record({ + 'name' : IDL.Text, + 'initial_config' : InitialConfig, + 'fallback_controller' : IDL.Opt(IDL.Principal), + 'upgrader' : SystemUpgraderInput, + }); + const SystemInstall = IDL.Variant({ + 'Upgrade' : SystemUpgrade, + 'Init' : SystemInit, + }); + const CancelRequestInput = IDL.Record({ + 'request_id' : UUID, + 'reason' : IDL.Opt(IDL.Text), + }); + const TimestampRFC3339 = IDL.Text; + const RequestStatus = IDL.Variant({ + 'Failed' : IDL.Record({ 'reason' : IDL.Opt(IDL.Text) }), + 'Approved' : IDL.Null, + 'Rejected' : IDL.Null, + 'Scheduled' : IDL.Record({ 'scheduled_at' : TimestampRFC3339 }), + 'Cancelled' : IDL.Record({ 'reason' : IDL.Opt(IDL.Text) }), + 'Processing' : IDL.Record({ 'started_at' : TimestampRFC3339 }), + 'Created' : IDL.Null, + 'Completed' : IDL.Record({ 'completed_at' : TimestampRFC3339 }), + }); + const RequestExecutionSchedule = IDL.Variant({ + 'Immediate' : IDL.Null, + 'Scheduled' : IDL.Record({ 'execution_time' : TimestampRFC3339 }), + }); + const RemoveAssetOperationInput = IDL.Record({ 'asset_id' : UUID }); + const RemoveAssetOperation = IDL.Record({ + 'input' : RemoveAssetOperationInput, + }); + const UserGroup = IDL.Record({ 'id' : UUID, 'name' : IDL.Text }); + const AddUserGroupOperationInput = IDL.Record({ 'name' : IDL.Text }); + const AddUserGroupOperation = IDL.Record({ + 'user_group' : IDL.Opt(UserGroup), + 'input' : AddUserGroupOperationInput, + }); + const EditPermissionOperationInput = IDL.Record({ + 'resource' : Resource, + 'user_groups' : IDL.Opt(IDL.Vec(UUID)), + 'auth_scope' : IDL.Opt(AuthScope), + 'users' : IDL.Opt(IDL.Vec(UUID)), + }); + const EditPermissionOperation = IDL.Record({ + 'input' : EditPermissionOperationInput, + }); + const SnapshotExternalCanisterOperationInput = IDL.Record({ + 'force' : IDL.Bool, + 'replace_snapshot' : IDL.Opt(IDL.Text), + 'canister_id' : IDL.Principal, + }); + const SnapshotExternalCanisterOperation = IDL.Record({ + 'input' : SnapshotExternalCanisterOperationInput, + 'snapshot_id' : IDL.Opt(IDL.Text), + }); + const PruneExternalCanisterOperationInput = IDL.Record({ + 'canister_id' : IDL.Principal, + 'prune' : IDL.Variant({ + 'snapshot' : IDL.Text, + 'state' : IDL.Null, + 'chunk_store' : IDL.Null, + }), + }); + const PruneExternalCanisterOperation = IDL.Record({ + 'input' : PruneExternalCanisterOperationInput, + }); + const EditNamedRuleOperationInput = IDL.Record({ + 'name' : IDL.Opt(IDL.Text), + 'rule' : IDL.Opt(RequestPolicyRule), + 'description' : IDL.Opt(IDL.Opt(IDL.Text)), + 'named_rule_id' : UUID, + }); + const EditNamedRuleOperation = IDL.Record({ + 'input' : EditNamedRuleOperationInput, + }); + const CanisterExecutionAndValidationMethodPair = IDL.Record({ + 'execution_method' : IDL.Text, + 'validation_method' : ValidationMethodResourceTarget, + }); + const ExternalCanisterCallPermission = IDL.Record({ + 'execution_method' : IDL.Text, + 'allow' : Allow, + 'validation_method' : ValidationMethodResourceTarget, + }); + const ExternalCanisterChangeCallPermissionsInput = IDL.Variant({ + 'OverrideSpecifiedByExecutionMethods' : IDL.Vec( + IDL.Record({ + 'execution_method' : IDL.Text, + 'permissions' : IDL.Vec( + IDL.Record({ + 'allow' : Allow, + 'validation_method' : ValidationMethodResourceTarget, + }) + ), + }) + ), + 'OverrideSpecifiedByExecutionValidationMethodPairs' : IDL.Vec( + IDL.Record({ + 'allow' : IDL.Opt(Allow), + 'method_configuration' : CanisterExecutionAndValidationMethodPair, + }) + ), + 'ReplaceAllBy' : IDL.Vec(ExternalCanisterCallPermission), + }); + const ExternalCanisterPermissionsUpdateInput = IDL.Record({ + 'calls' : IDL.Opt(ExternalCanisterChangeCallPermissionsInput), + 'read' : IDL.Opt(Allow), + 'change' : IDL.Opt(Allow), + }); + const ExternalCanisterChangeRequestPolicyRuleInput = IDL.Record({ + 'rule' : RequestPolicyRule, + 'policy_id' : IDL.Opt(UUID), + }); + const ExternalCanisterCallRequestPolicyRuleInput = IDL.Record({ + 'execution_method' : IDL.Text, + 'rule' : RequestPolicyRule, + 'validation_method' : ValidationMethodResourceTarget, + 'policy_id' : IDL.Opt(UUID), + }); + const ExternalCanisterChangeCallRequestPoliciesInput = IDL.Variant({ + 'RemoveByPolicyIds' : IDL.Vec(UUID), + 'OverrideSpecifiedByExecutionMethods' : IDL.Vec( + IDL.Record({ + 'execution_method' : IDL.Text, + 'policies' : IDL.Vec( + IDL.Record({ + 'rule' : RequestPolicyRule, + 'validation_method' : ValidationMethodResourceTarget, + 'policy_id' : IDL.Opt(UUID), + }) + ), + }) + ), + 'OverrideSpecifiedByExecutionValidationMethodPairs' : IDL.Vec( + IDL.Record({ + 'method_configuration' : CanisterExecutionAndValidationMethodPair, + 'policies' : IDL.Vec(ExternalCanisterChangeRequestPolicyRuleInput), + }) + ), + 'ReplaceAllBy' : IDL.Vec(ExternalCanisterCallRequestPolicyRuleInput), + }); + const ExternalCanisterRequestPoliciesUpdateInput = IDL.Record({ + 'calls' : IDL.Opt(ExternalCanisterChangeCallRequestPoliciesInput), + 'change' : IDL.Opt(IDL.Vec(ExternalCanisterChangeRequestPolicyRuleInput)), + }); + const ExternalCanisterState = IDL.Variant({ + 'Active' : IDL.Null, + 'Archived' : IDL.Null, + }); + const ExternalCanisterMetadata = IDL.Record({ + 'key' : IDL.Text, + 'value' : IDL.Text, + }); + const ChangeExternalCanisterMetadata = IDL.Variant({ + 'OverrideSpecifiedBy' : IDL.Vec(ExternalCanisterMetadata), + 'RemoveKeys' : IDL.Vec(IDL.Text), + 'ReplaceAllBy' : IDL.Vec(ExternalCanisterMetadata), + }); + const ConfigureExternalCanisterSettingsInput = IDL.Record({ + 'permissions' : IDL.Opt(ExternalCanisterPermissionsUpdateInput), + 'name' : IDL.Opt(IDL.Text), + 'labels' : IDL.Opt(IDL.Vec(IDL.Text)), + 'description' : IDL.Opt(IDL.Text), + 'request_policies' : IDL.Opt(ExternalCanisterRequestPoliciesUpdateInput), + 'state' : IDL.Opt(ExternalCanisterState), + 'change_metadata' : IDL.Opt(ChangeExternalCanisterMetadata), + }); + const LogVisibility = IDL.Variant({ + 'controllers' : IDL.Null, + 'public' : IDL.Null, + 'allowed_viewers' : IDL.Vec(IDL.Principal), + }); + const DefiniteCanisterSettingsInput = IDL.Record({ + 'freezing_threshold' : IDL.Opt(IDL.Nat), + 'controllers' : IDL.Opt(IDL.Vec(IDL.Principal)), + 'reserved_cycles_limit' : IDL.Opt(IDL.Nat), + 'log_visibility' : IDL.Opt(LogVisibility), + 'wasm_memory_limit' : IDL.Opt(IDL.Nat), + 'memory_allocation' : IDL.Opt(IDL.Nat), + 'compute_allocation' : IDL.Opt(IDL.Nat), + }); + const ConfigureExternalCanisterOperationKind = IDL.Variant({ + 'SoftDelete' : IDL.Null, + 'Settings' : ConfigureExternalCanisterSettingsInput, + 'Delete' : IDL.Null, + 'NativeSettings' : DefiniteCanisterSettingsInput, + }); + const ConfigureExternalCanisterOperationInput = IDL.Record({ + 'kind' : ConfigureExternalCanisterOperationKind, + 'canister_id' : IDL.Principal, + }); + const ConfigureExternalCanisterOperation = ConfigureExternalCanisterOperationInput; + const CanisterInstallMode = IDL.Variant({ + 'reinstall' : IDL.Null, + 'upgrade' : IDL.Opt( + IDL.Record({ + 'wasm_memory_persistence' : IDL.Opt( + IDL.Variant({ 'keep' : IDL.Null, 'replace' : IDL.Null }) + ), + 'skip_pre_upgrade' : IDL.Opt(IDL.Bool), + }) + ), + 'install' : IDL.Null, + }); + const Sha256Hash = IDL.Text; + const ChangeExternalCanisterOperation = IDL.Record({ + 'mode' : CanisterInstallMode, + 'canister_id' : IDL.Principal, + 'module_checksum' : Sha256Hash, + 'arg_checksum' : IDL.Opt(Sha256Hash), + }); + const CycleObtainStrategyInput = IDL.Variant({ + 'Disabled' : IDL.Null, + 'MintFromNativeToken' : IDL.Record({ 'account_id' : UUID }), + 'WithdrawFromCyclesLedger' : IDL.Record({ 'account_id' : UUID }), + }); + const MonitoringExternalCanisterCyclesThresholdInput = IDL.Record({ + 'fund_cycles' : IDL.Nat, + 'min_cycles' : IDL.Nat, + }); + const MonitoringExternalCanisterEstimatedRuntimeInput = IDL.Record({ + 'fund_runtime_secs' : IDL.Nat64, + 'fallback_min_cycles' : IDL.Nat, + 'min_runtime_secs' : IDL.Nat64, + 'fallback_fund_cycles' : IDL.Nat, + 'max_runtime_cycles_fund' : IDL.Nat, + }); + const MonitorExternalCanisterStrategyInput = IDL.Variant({ + 'Always' : IDL.Nat, + 'BelowThreshold' : MonitoringExternalCanisterCyclesThresholdInput, + 'BelowEstimatedRuntime' : MonitoringExternalCanisterEstimatedRuntimeInput, + }); + const MonitorExternalCanisterStartInput = IDL.Record({ + 'cycle_obtain_strategy' : IDL.Opt(CycleObtainStrategyInput), + 'funding_strategy' : MonitorExternalCanisterStrategyInput, + }); + const MonitorExternalCanisterOperationKind = IDL.Variant({ + 'Start' : MonitorExternalCanisterStartInput, + 'Stop' : IDL.Null, + }); + const MonitorExternalCanisterOperationInput = IDL.Record({ + 'kind' : MonitorExternalCanisterOperationKind, + 'canister_id' : IDL.Principal, + }); + const MonitorExternalCanisterOperation = MonitorExternalCanisterOperationInput; + const User = IDL.Record({ + 'id' : UUID, + 'status' : UserStatus, + 'groups' : IDL.Vec(UserGroup), + 'name' : IDL.Text, + 'last_modification_timestamp' : TimestampRFC3339, + 'identities' : IDL.Vec(IDL.Principal), + }); + const AddUserOperationInput = IDL.Record({ + 'status' : UserStatus, + 'groups' : IDL.Vec(UUID), + 'name' : IDL.Text, + 'identities' : IDL.Vec(IDL.Principal), + }); + const AddUserOperation = IDL.Record({ + 'user' : IDL.Opt(User), + 'input' : AddUserOperationInput, + }); + const ChangeMetadata = IDL.Variant({ + 'OverrideSpecifiedBy' : IDL.Vec(AssetMetadata), + 'RemoveKeys' : IDL.Vec(IDL.Text), + 'ReplaceAllBy' : IDL.Vec(AssetMetadata), + }); + const AssetSymbol = IDL.Text; + const EditAssetOperationInput = IDL.Record({ + 'standards' : IDL.Opt(IDL.Vec(IDL.Text)), + 'name' : IDL.Opt(IDL.Text), + 'blockchain' : IDL.Opt(IDL.Text), + 'change_metadata' : IDL.Opt(ChangeMetadata), + 'asset_id' : UUID, + 'symbol' : IDL.Opt(AssetSymbol), + }); + const EditAssetOperation = IDL.Record({ 'input' : EditAssetOperationInput }); + const EditUserGroupOperationInput = IDL.Record({ + 'name' : IDL.Text, + 'user_group_id' : UUID, + }); + const EditUserGroupOperation = IDL.Record({ + 'input' : EditUserGroupOperationInput, + }); + const SetDisasterRecoveryOperation = IDL.Record({ + 'committee' : IDL.Opt(DisasterRecoveryCommittee), + }); + const EditRequestPolicyOperationInput = IDL.Record({ + 'rule' : IDL.Opt(RequestPolicyRule), + 'specifier' : IDL.Opt(RequestSpecifier), + 'policy_id' : UUID, + }); + const EditRequestPolicyOperation = IDL.Record({ + 'input' : EditRequestPolicyOperationInput, + }); + const RemoveRequestPolicyOperationInput = IDL.Record({ 'policy_id' : UUID }); + const RemoveRequestPolicyOperation = IDL.Record({ + 'input' : RemoveRequestPolicyOperationInput, + }); + const Asset = IDL.Record({ + 'id' : UUID, + 'decimals' : IDL.Nat32, + 'standards' : IDL.Vec(IDL.Text), + 'metadata' : IDL.Vec(AssetMetadata), + 'name' : IDL.Text, + 'blockchain' : IDL.Text, + 'symbol' : AssetSymbol, + }); + const AddAssetOperationInput = IDL.Record({ + 'decimals' : IDL.Nat32, + 'standards' : IDL.Vec(IDL.Text), + 'metadata' : IDL.Vec(AssetMetadata), + 'name' : IDL.Text, + 'blockchain' : IDL.Text, + 'symbol' : AssetSymbol, + }); + const AddAssetOperation = IDL.Record({ + 'asset' : IDL.Opt(Asset), + 'input' : AddAssetOperationInput, + }); + const SystemUpgradeTarget = IDL.Variant({ + 'UpgradeUpgrader' : IDL.Null, + 'UpgradeStation' : IDL.Null, + }); + const SystemUpgradeOperation = IDL.Record({ + 'take_backup_snapshot' : IDL.Opt(IDL.Bool), + 'module_checksum' : Sha256Hash, + 'target' : SystemUpgradeTarget, + 'arg_checksum' : IDL.Opt(Sha256Hash), + }); + const RemoveAddressBookEntryOperationInput = IDL.Record({ + 'address_book_entry_id' : UUID, + }); + const RemoveAddressBookEntryOperation = IDL.Record({ + 'input' : RemoveAddressBookEntryOperationInput, + }); + const SystemRestoreTarget = IDL.Variant({ + 'RestoreUpgrader' : IDL.Null, + 'RestoreStation' : IDL.Null, + }); + const SystemRestoreOperationInput = IDL.Record({ + 'target' : SystemRestoreTarget, + 'snapshot_id' : IDL.Text, + }); + const SystemRestoreOperation = IDL.Record({ + 'input' : SystemRestoreOperationInput, + }); + const ExternalCanisterPermissions = IDL.Record({ + 'calls' : IDL.Vec(ExternalCanisterCallPermission), + 'read' : Allow, + 'change' : Allow, + }); + const ExternalCanisterPermissionsCreateInput = ExternalCanisterPermissions; + const CreateExternalCanisterOperationKindAddExisting = IDL.Record({ + 'canister_id' : IDL.Principal, + }); + const SubnetFilter = IDL.Record({ 'subnet_type' : IDL.Opt(IDL.Text) }); + const SubnetSelection = IDL.Variant({ + 'Filter' : SubnetFilter, + 'Subnet' : IDL.Record({ 'subnet' : IDL.Principal }), + }); + const CreateExternalCanisterOperationKindCreateNew = IDL.Record({ + 'initial_cycles' : IDL.Opt(IDL.Nat64), + 'subnet_selection' : IDL.Opt(SubnetSelection), + }); + const CreateExternalCanisterOperationKind = IDL.Variant({ + 'AddExisting' : CreateExternalCanisterOperationKindAddExisting, + 'CreateNew' : CreateExternalCanisterOperationKindCreateNew, + }); + const ExternalCanisterRequestPoliciesCreateInput = IDL.Record({ + 'calls' : IDL.Vec(ExternalCanisterCallRequestPolicyRuleInput), + 'change' : IDL.Vec(ExternalCanisterChangeRequestPolicyRuleInput), + }); + const CreateExternalCanisterOperationInput = IDL.Record({ + 'permissions' : ExternalCanisterPermissionsCreateInput, + 'metadata' : IDL.Opt(IDL.Vec(ExternalCanisterMetadata)), + 'kind' : CreateExternalCanisterOperationKind, + 'name' : IDL.Text, + 'labels' : IDL.Opt(IDL.Vec(IDL.Text)), + 'description' : IDL.Opt(IDL.Text), + 'request_policies' : ExternalCanisterRequestPoliciesCreateInput, + }); + const CreateExternalCanisterOperation = IDL.Record({ + 'canister_id' : IDL.Opt(IDL.Principal), + 'input' : CreateExternalCanisterOperationInput, + }); + const ChangeAddressBookMetadata = IDL.Variant({ + 'OverrideSpecifiedBy' : IDL.Vec(AddressBookMetadata), + 'RemoveKeys' : IDL.Vec(IDL.Text), + 'ReplaceAllBy' : IDL.Vec(AddressBookMetadata), + }); + const EditAddressBookEntryOperationInput = IDL.Record({ + 'labels' : IDL.Opt(IDL.Vec(IDL.Text)), + 'change_metadata' : IDL.Opt(ChangeAddressBookMetadata), + 'address_book_entry_id' : UUID, + 'address_owner' : IDL.Opt(IDL.Text), + }); + const EditAddressBookEntryOperation = IDL.Record({ + 'input' : EditAddressBookEntryOperationInput, + }); + const FundExternalCanisterSendCyclesInput = IDL.Record({ + 'cycles' : IDL.Nat64, + }); + const FundExternalCanisterOperationKind = IDL.Variant({ + 'Send' : FundExternalCanisterSendCyclesInput, + }); + const FundExternalCanisterOperationInput = IDL.Record({ + 'kind' : FundExternalCanisterOperationKind, + 'canister_id' : IDL.Principal, + }); + const FundExternalCanisterOperation = FundExternalCanisterOperationInput; + const EditUserOperationInput = IDL.Record({ + 'id' : UUID, + 'status' : IDL.Opt(UserStatus), + 'groups' : IDL.Opt(IDL.Vec(UUID)), + 'cancel_pending_requests' : IDL.Opt(IDL.Bool), + 'name' : IDL.Opt(IDL.Text), + 'identities' : IDL.Opt(IDL.Vec(IDL.Principal)), + }); + const EditUserOperation = IDL.Record({ 'input' : EditUserOperationInput }); + const ManageSystemInfoOperationInput = IDL.Record({ + 'name' : IDL.Opt(IDL.Text), + 'cycle_obtain_strategy' : IDL.Opt(CycleObtainStrategyInput), + 'max_upgrader_backup_snapshots' : IDL.Opt(IDL.Nat64), + 'max_station_backup_snapshots' : IDL.Opt(IDL.Nat64), + }); + const ManageSystemInfoOperation = IDL.Record({ + 'input' : ManageSystemInfoOperationInput, + }); + const NetworkId = IDL.Text; + const Network = IDL.Record({ 'id' : NetworkId, 'name' : IDL.Text }); + const AccountBalance = IDL.Record({ + 'account_id' : UUID, + 'decimals' : IDL.Nat32, + 'balance' : IDL.Nat, + 'last_update_timestamp' : TimestampRFC3339, + 'query_state' : IDL.Text, + 'asset_id' : UUID, + }); + const AccountAsset = IDL.Record({ + 'balance' : IDL.Opt(AccountBalance), + 'asset_id' : UUID, + }); + const AccountAddress = IDL.Record({ + 'address' : IDL.Text, + 'format' : IDL.Text, + }); + const Account = IDL.Record({ + 'id' : UUID, + 'configs_request_policy' : IDL.Opt(RequestPolicyRule), + 'metadata' : IDL.Vec(AccountMetadata), + 'name' : IDL.Text, + 'assets' : IDL.Vec(AccountAsset), + 'addresses' : IDL.Vec(AccountAddress), + 'transfer_request_policy' : IDL.Opt(RequestPolicyRule), + 'last_modification_timestamp' : TimestampRFC3339, + }); + const TransferMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text }); + const TransferOperationInput = IDL.Record({ + 'to' : IDL.Text, + 'fee' : IDL.Opt(IDL.Nat), + 'with_standard' : IDL.Text, + 'from_account_id' : UUID, + 'metadata' : IDL.Vec(TransferMetadata), + 'network' : IDL.Opt(Network), + 'amount' : IDL.Nat, + 'from_asset_id' : UUID, + }); + const TransferOperation = IDL.Record({ + 'fee' : IDL.Opt(IDL.Nat), + 'from_asset' : Asset, + 'network' : Network, + 'transfer_id' : IDL.Opt(UUID), + 'from_account' : IDL.Opt(Account), + 'input' : TransferOperationInput, + }); + const RequestPolicyRuleInput = IDL.Variant({ + 'Set' : RequestPolicyRule, + 'Remove' : IDL.Null, + }); + const ChangeAssets = IDL.Variant({ + 'ReplaceWith' : IDL.Record({ 'assets' : IDL.Vec(UUID) }), + 'Change' : IDL.Record({ + 'add_assets' : IDL.Vec(UUID), + 'remove_assets' : IDL.Vec(UUID), + }), + }); + const EditAccountOperationInput = IDL.Record({ + 'account_id' : UUID, + 'configs_request_policy' : IDL.Opt(RequestPolicyRuleInput), + 'read_permission' : IDL.Opt(Allow), + 'configs_permission' : IDL.Opt(Allow), + 'name' : IDL.Opt(IDL.Text), + 'change_assets' : IDL.Opt(ChangeAssets), + 'transfer_request_policy' : IDL.Opt(RequestPolicyRuleInput), + 'transfer_permission' : IDL.Opt(Allow), + }); + const EditAccountOperation = IDL.Record({ + 'input' : EditAccountOperationInput, + }); + const AddressBookEntry = IDL.Record({ + 'id' : UUID, + 'metadata' : IDL.Vec(AddressBookMetadata), + 'labels' : IDL.Vec(IDL.Text), + 'blockchain' : IDL.Text, + 'address' : IDL.Text, + 'last_modification_timestamp' : IDL.Text, + 'address_format' : IDL.Text, + 'address_owner' : IDL.Text, + }); + const AddAddressBookEntryOperationInput = IDL.Record({ + 'metadata' : IDL.Vec(AddressBookMetadata), + 'labels' : IDL.Vec(IDL.Text), + 'blockchain' : IDL.Text, + 'address' : IDL.Text, + 'address_format' : IDL.Text, + 'address_owner' : IDL.Text, + }); + const AddAddressBookEntryOperation = IDL.Record({ + 'address_book_entry' : IDL.Opt(AddressBookEntry), + 'input' : AddAddressBookEntryOperationInput, + }); + const AddRequestPolicyOperationInput = IDL.Record({ + 'rule' : RequestPolicyRule, + 'specifier' : RequestSpecifier, + }); + const AddRequestPolicyOperation = IDL.Record({ + 'input' : AddRequestPolicyOperationInput, + 'policy_id' : IDL.Opt(UUID), + }); + const RemoveNamedRuleOperationInput = IDL.Record({ 'named_rule_id' : UUID }); + const RemoveNamedRuleOperation = IDL.Record({ + 'input' : RemoveNamedRuleOperationInput, + }); + const RemoveUserGroupOperationInput = IDL.Record({ 'user_group_id' : UUID }); + const RemoveUserGroupOperation = IDL.Record({ + 'input' : RemoveUserGroupOperationInput, + }); + const CallExternalCanisterOperation = IDL.Record({ + 'arg' : IDL.Opt(IDL.Vec(IDL.Nat8)), + 'execution_method' : CanisterMethod, + 'validation_method' : IDL.Opt(CanisterMethod), + 'arg_checksum' : IDL.Opt(Sha256Hash), + 'execution_method_cycles' : IDL.Opt(IDL.Nat64), + 'arg_rendering' : IDL.Opt(IDL.Text), + 'execution_method_reply' : IDL.Opt(IDL.Vec(IDL.Nat8)), + }); + const NamedRule = IDL.Record({ + 'id' : UUID, + 'name' : IDL.Text, + 'rule' : RequestPolicyRule, + 'description' : IDL.Opt(IDL.Text), + }); + const AddNamedRuleOperationInput = IDL.Record({ + 'name' : IDL.Text, + 'rule' : RequestPolicyRule, + 'description' : IDL.Opt(IDL.Text), + }); + const AddNamedRuleOperation = IDL.Record({ + 'named_rule' : IDL.Opt(NamedRule), + 'input' : AddNamedRuleOperationInput, + }); + const RestoreExternalCanisterOperationInput = IDL.Record({ + 'canister_id' : IDL.Principal, + 'snapshot_id' : IDL.Text, + }); + const RestoreExternalCanisterOperation = IDL.Record({ + 'input' : RestoreExternalCanisterOperationInput, + }); + const AddAccountOperationInput = IDL.Record({ + 'configs_request_policy' : IDL.Opt(RequestPolicyRule), + 'read_permission' : Allow, + 'configs_permission' : Allow, + 'metadata' : IDL.Vec(AccountMetadata), + 'name' : IDL.Text, + 'assets' : IDL.Vec(UUID), + 'transfer_request_policy' : IDL.Opt(RequestPolicyRule), + 'transfer_permission' : Allow, + }); + const AddAccountOperation = IDL.Record({ + 'account' : IDL.Opt(Account), + 'input' : AddAccountOperationInput, + }); + const RequestOperation = IDL.Variant({ + 'RemoveAsset' : RemoveAssetOperation, + 'AddUserGroup' : AddUserGroupOperation, + 'EditPermission' : EditPermissionOperation, + 'SnapshotExternalCanister' : SnapshotExternalCanisterOperation, + 'PruneExternalCanister' : PruneExternalCanisterOperation, + 'EditNamedRule' : EditNamedRuleOperation, + 'ConfigureExternalCanister' : ConfigureExternalCanisterOperation, + 'ChangeExternalCanister' : ChangeExternalCanisterOperation, + 'MonitorExternalCanister' : MonitorExternalCanisterOperation, + 'AddUser' : AddUserOperation, + 'EditAsset' : EditAssetOperation, + 'EditUserGroup' : EditUserGroupOperation, + 'SetDisasterRecovery' : SetDisasterRecoveryOperation, + 'EditRequestPolicy' : EditRequestPolicyOperation, + 'RemoveRequestPolicy' : RemoveRequestPolicyOperation, + 'AddAsset' : AddAssetOperation, + 'SystemUpgrade' : SystemUpgradeOperation, + 'RemoveAddressBookEntry' : RemoveAddressBookEntryOperation, + 'SystemRestore' : SystemRestoreOperation, + 'CreateExternalCanister' : CreateExternalCanisterOperation, + 'EditAddressBookEntry' : EditAddressBookEntryOperation, + 'FundExternalCanister' : FundExternalCanisterOperation, + 'EditUser' : EditUserOperation, + 'ManageSystemInfo' : ManageSystemInfoOperation, + 'Transfer' : TransferOperation, + 'EditAccount' : EditAccountOperation, + 'AddAddressBookEntry' : AddAddressBookEntryOperation, + 'AddRequestPolicy' : AddRequestPolicyOperation, + 'RemoveNamedRule' : RemoveNamedRuleOperation, + 'RemoveUserGroup' : RemoveUserGroupOperation, + 'CallExternalCanister' : CallExternalCanisterOperation, + 'AddNamedRule' : AddNamedRuleOperation, + 'RestoreExternalCanister' : RestoreExternalCanisterOperation, + 'AddAccount' : AddAccountOperation, + }); + const RequestApprovalStatus = IDL.Variant({ + 'Approved' : IDL.Null, + 'Rejected' : IDL.Null, + }); + const RequestApproval = IDL.Record({ + 'status' : RequestApprovalStatus, + 'approver_id' : UUID, + 'status_reason' : IDL.Opt(IDL.Text), + 'decided_at' : TimestampRFC3339, + }); + const Request = IDL.Record({ + 'id' : UUID, + 'status' : RequestStatus, + 'title' : IDL.Text, + 'execution_plan' : RequestExecutionSchedule, + 'expiration_dt' : TimestampRFC3339, + 'deduplication_key' : IDL.Opt(IDL.Text), + 'tags' : IDL.Vec(IDL.Text), + 'created_at' : TimestampRFC3339, + 'requested_by' : UUID, + 'summary' : IDL.Opt(IDL.Text), + 'operation' : RequestOperation, + 'approvals' : IDL.Vec(RequestApproval), + }); + const Error = IDL.Record({ + 'code' : IDL.Text, + 'message' : IDL.Opt(IDL.Text), + 'details' : IDL.Opt(IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text))), + }); + const CancelRequestResult = IDL.Variant({ + 'Ok' : IDL.Record({ 'request' : Request }), + 'Err' : Error, + }); + const CanisterSnapshotsInput = IDL.Record({ 'canister_id' : IDL.Principal }); + const CanisterSnapshotsResponse = IDL.Vec( + IDL.Record({ + 'total_size' : IDL.Nat64, + 'taken_at_timestamp' : TimestampRFC3339, + 'snapshot_id' : IDL.Text, + }) + ); + const CanisterSnapshotsResult = IDL.Variant({ + 'Ok' : CanisterSnapshotsResponse, + 'Err' : Error, + }); + const CanisterStatusInput = IDL.Record({ 'canister_id' : IDL.Principal }); + const DefiniteCanisterSettings = IDL.Record({ + 'freezing_threshold' : IDL.Nat, + 'controllers' : IDL.Vec(IDL.Principal), + 'reserved_cycles_limit' : IDL.Nat, + 'log_visibility' : LogVisibility, + 'wasm_memory_limit' : IDL.Nat, + 'memory_allocation' : IDL.Nat, + 'compute_allocation' : IDL.Nat, + }); + const CanisterStatusResponse = IDL.Record({ + 'status' : IDL.Variant({ + 'stopped' : IDL.Null, + 'stopping' : IDL.Null, + 'running' : IDL.Null, + }), + 'memory_size' : IDL.Nat, + 'cycles' : IDL.Nat, + 'settings' : DefiniteCanisterSettings, + 'query_stats' : IDL.Record({ + 'response_payload_bytes_total' : IDL.Nat, + 'num_instructions_total' : IDL.Nat, + 'num_calls_total' : IDL.Nat, + 'request_payload_bytes_total' : IDL.Nat, + }), + 'idle_cycles_burned_per_day' : IDL.Nat, + 'module_hash' : IDL.Opt(IDL.Vec(IDL.Nat8)), + 'reserved_cycles' : IDL.Nat, + }); + const StandardData = IDL.Record({ + 'supported_operations' : IDL.Vec(IDL.Text), + 'supported_address_formats' : IDL.Vec(IDL.Text), + 'required_metadata_fields' : IDL.Vec(IDL.Text), + 'standard' : IDL.Text, + }); + const SupportedBlockchain = IDL.Record({ + 'blockchain' : IDL.Text, + 'supported_standards' : IDL.Vec(StandardData), + }); + const Capabilities = IDL.Record({ + 'name' : IDL.Text, + 'version' : IDL.Text, + 'supported_assets' : IDL.Vec(Asset), + 'supported_blockchains' : IDL.Vec(SupportedBlockchain), + }); + const CapabilitiesResult = IDL.Variant({ + 'Ok' : IDL.Record({ 'capabilities' : Capabilities }), + 'Err' : Error, + }); + const WasmModuleExtraChunks = IDL.Record({ + 'wasm_module_hash' : IDL.Vec(IDL.Nat8), + 'store_canister' : IDL.Principal, + 'extra_chunks_key' : IDL.Text, + }); + const ChangeExternalCanisterOperationInput = IDL.Record({ + 'arg' : IDL.Opt(IDL.Vec(IDL.Nat8)), + 'module_extra_chunks' : IDL.Opt(WasmModuleExtraChunks), + 'mode' : CanisterInstallMode, + 'canister_id' : IDL.Principal, + 'module' : IDL.Vec(IDL.Nat8), + }); + const SetDisasterRecoveryOperationInput = IDL.Record({ + 'committee' : IDL.Opt(DisasterRecoveryCommittee), + }); + const SystemUpgradeOperationInput = IDL.Record({ + 'arg' : IDL.Opt(IDL.Vec(IDL.Nat8)), + 'module_extra_chunks' : IDL.Opt(WasmModuleExtraChunks), + 'take_backup_snapshot' : IDL.Opt(IDL.Bool), + 'target' : SystemUpgradeTarget, + 'module' : IDL.Vec(IDL.Nat8), + }); + const CallExternalCanisterOperationInput = IDL.Record({ + 'arg' : IDL.Opt(IDL.Vec(IDL.Nat8)), + 'execution_method' : CanisterMethod, + 'validation_method' : IDL.Opt(CanisterMethod), + 'execution_method_cycles' : IDL.Opt(IDL.Nat64), + }); + const RequestOperationInput = IDL.Variant({ + 'RemoveAsset' : RemoveAssetOperationInput, + 'AddUserGroup' : AddUserGroupOperationInput, + 'EditPermission' : EditPermissionOperationInput, + 'SnapshotExternalCanister' : SnapshotExternalCanisterOperationInput, + 'PruneExternalCanister' : PruneExternalCanisterOperationInput, + 'EditNamedRule' : EditNamedRuleOperationInput, + 'ConfigureExternalCanister' : ConfigureExternalCanisterOperationInput, + 'ChangeExternalCanister' : ChangeExternalCanisterOperationInput, + 'MonitorExternalCanister' : MonitorExternalCanisterOperationInput, + 'AddUser' : AddUserOperationInput, + 'EditAsset' : EditAssetOperationInput, + 'EditUserGroup' : EditUserGroupOperationInput, + 'SetDisasterRecovery' : SetDisasterRecoveryOperationInput, + 'EditRequestPolicy' : EditRequestPolicyOperationInput, + 'RemoveRequestPolicy' : RemoveRequestPolicyOperationInput, + 'AddAsset' : AddAssetOperationInput, + 'SystemUpgrade' : SystemUpgradeOperationInput, + 'RemoveAddressBookEntry' : RemoveAddressBookEntryOperationInput, + 'SystemRestore' : SystemRestoreOperationInput, + 'CreateExternalCanister' : CreateExternalCanisterOperationInput, + 'EditAddressBookEntry' : EditAddressBookEntryOperationInput, + 'FundExternalCanister' : FundExternalCanisterOperationInput, + 'EditUser' : EditUserOperationInput, + 'ManageSystemInfo' : ManageSystemInfoOperationInput, + 'Transfer' : TransferOperationInput, + 'EditAccount' : EditAccountOperationInput, + 'AddAddressBookEntry' : AddAddressBookEntryOperationInput, + 'AddRequestPolicy' : AddRequestPolicyOperationInput, + 'RemoveNamedRule' : RemoveNamedRuleOperationInput, + 'RemoveUserGroup' : RemoveUserGroupOperationInput, + 'CallExternalCanister' : CallExternalCanisterOperationInput, + 'AddNamedRule' : AddNamedRuleOperationInput, + 'RestoreExternalCanister' : RestoreExternalCanisterOperationInput, + 'AddAccount' : AddAccountOperationInput, + }); + const CreateRequestInput = IDL.Record({ + 'title' : IDL.Opt(IDL.Text), + 'execution_plan' : IDL.Opt(RequestExecutionSchedule), + 'expiration_dt' : IDL.Opt(TimestampRFC3339), + 'deduplication_key' : IDL.Opt(IDL.Text), + 'tags' : IDL.Opt(IDL.Vec(IDL.Text)), + 'summary' : IDL.Opt(IDL.Text), + 'operation' : RequestOperationInput, + }); + const RequestCallerPrivileges = IDL.Record({ + 'id' : UUID, + 'can_approve' : IDL.Bool, + }); + const EvaluationStatus = IDL.Variant({ + 'Approved' : IDL.Null, + 'Rejected' : IDL.Null, + 'Pending' : IDL.Null, + }); + const EvaluationSummaryReason = IDL.Variant({ + 'AllowList' : IDL.Null, + 'AllowListMetadata' : IDL.Null, + 'AutoApproved' : IDL.Null, + 'ApprovalQuorum' : IDL.Null, + }); + const EvaluatedRequestPolicyRule = IDL.Variant({ + 'Not' : RequestPolicyRuleResult, + 'Quorum' : IDL.Record({ + 'total_possible_approvers' : IDL.Nat64, + 'min_approved' : IDL.Nat64, + 'approvers' : IDL.Vec(UUID), + }), + 'AllowListed' : IDL.Null, + 'QuorumPercentage' : IDL.Record({ + 'total_possible_approvers' : IDL.Nat64, + 'min_approved' : IDL.Nat64, + 'approvers' : IDL.Vec(UUID), + }), + 'AutoApproved' : IDL.Null, + 'AllOf' : IDL.Vec(RequestPolicyRuleResult), + 'AnyOf' : IDL.Vec(RequestPolicyRuleResult), + 'AllowListedByMetadata' : IDL.Record({ 'metadata' : AddressBookMetadata }), + }); + RequestPolicyRuleResult.fill( + IDL.Record({ + 'status' : EvaluationStatus, + 'evaluated_rule' : EvaluatedRequestPolicyRule, + }) + ); + const RequestEvaluationResult = IDL.Record({ + 'request_id' : UUID, + 'status' : EvaluationStatus, + 'result_reasons' : IDL.Opt(IDL.Vec(EvaluationSummaryReason)), + 'policy_results' : IDL.Vec(RequestPolicyRuleResult), + }); + const DisplayUser = IDL.Record({ 'id' : UUID, 'name' : IDL.Text }); + const RequestAdditionalInfo = IDL.Record({ + 'id' : UUID, + 'evaluation_result' : IDL.Opt(RequestEvaluationResult), + 'requester_name' : IDL.Text, + 'approvers' : IDL.Vec(DisplayUser), + }); + const CreateRequestResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'privileges' : RequestCallerPrivileges, + 'request' : Request, + 'additional_info' : RequestAdditionalInfo, + }), + 'Err' : Error, + }); + const FetchAccountBalancesInput = IDL.Record({ + 'account_ids' : IDL.Vec(UUID), + }); + const FetchAccountBalancesResult = IDL.Variant({ + 'Ok' : IDL.Record({ 'balances' : IDL.Vec(IDL.Opt(AccountBalance)) }), + 'Err' : Error, + }); + const GetAccountInput = IDL.Record({ 'account_id' : UUID }); + const AccountCallerPrivileges = IDL.Record({ + 'id' : UUID, + 'can_transfer' : IDL.Bool, + 'can_edit' : IDL.Bool, + }); + const GetAccountResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'privileges' : AccountCallerPrivileges, + 'account' : Account, + }), + 'Err' : Error, + }); + const GetAddressBookEntryInput = IDL.Record({ + 'address_book_entry_id' : UUID, + }); + const AddressBookEntryCallerPrivileges = IDL.Record({ + 'id' : UUID, + 'can_delete' : IDL.Bool, + 'can_edit' : IDL.Bool, + }); + const GetAddressBookEntryResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'privileges' : AddressBookEntryCallerPrivileges, + 'address_book_entry' : AddressBookEntry, + }), + 'Err' : Error, + }); + const GetAssetInput = IDL.Record({ 'asset_id' : UUID }); + const AssetCallerPrivileges = IDL.Record({ + 'id' : UUID, + 'can_delete' : IDL.Bool, + 'can_edit' : IDL.Bool, + }); + const GetAssetResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'privileges' : AssetCallerPrivileges, + 'asset' : Asset, + }), + 'Err' : Error, + }); + const GetExternalCanisterInput = IDL.Record({ + 'canister_id' : IDL.Principal, + }); + const ExternalCanisterCallerMethodsPrivileges = IDL.Record({ + 'execution_method' : IDL.Text, + 'validation_method' : ValidationMethodResourceTarget, + }); + const ExternalCanisterCallerPrivileges = IDL.Record({ + 'id' : UUID, + 'can_change' : IDL.Bool, + 'canister_id' : IDL.Principal, + 'can_call' : IDL.Vec(ExternalCanisterCallerMethodsPrivileges), + 'can_fund' : IDL.Bool, + }); + const ExternalCanisterCallRequestPolicyRule = IDL.Record({ + 'execution_method' : IDL.Text, + 'rule' : RequestPolicyRule, + 'validation_method' : ValidationMethodResourceTarget, + 'policy_id' : UUID, + }); + const ExternalCanisterChangeRequestPolicyRule = IDL.Record({ + 'rule' : RequestPolicyRule, + 'policy_id' : UUID, + }); + const ExternalCanisterRequestPolicies = IDL.Record({ + 'calls' : IDL.Vec(ExternalCanisterCallRequestPolicyRule), + 'change' : IDL.Vec(ExternalCanisterChangeRequestPolicyRule), + }); + const ExternalCanister = IDL.Record({ + 'id' : UUID, + 'permissions' : ExternalCanisterPermissions, + 'modified_at' : IDL.Opt(TimestampRFC3339), + 'metadata' : IDL.Vec(ExternalCanisterMetadata), + 'name' : IDL.Text, + 'labels' : IDL.Vec(IDL.Text), + 'canister_id' : IDL.Principal, + 'description' : IDL.Opt(IDL.Text), + 'created_at' : TimestampRFC3339, + 'request_policies' : ExternalCanisterRequestPolicies, + 'state' : ExternalCanisterState, + 'monitoring' : IDL.Opt(MonitorExternalCanisterStartInput), + }); + const GetExternalCanisterResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'privileges' : ExternalCanisterCallerPrivileges, + 'canister' : ExternalCanister, + }), + 'Err' : Error, + }); + const GetExternalCanisterFiltersInput = IDL.Record({ + 'with_labels' : IDL.Opt(IDL.Bool), + 'with_name' : IDL.Opt(IDL.Record({ 'prefix' : IDL.Opt(IDL.Text) })), + }); + const GetExternalCanisterFiltersResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'labels' : IDL.Opt(IDL.Vec(IDL.Text)), + 'names' : IDL.Opt( + IDL.Vec( + IDL.Record({ 'name' : IDL.Text, 'canister_id' : IDL.Principal }) + ) + ), + }), + 'Err' : Error, + }); + const GetNamedRuleInput = IDL.Record({ 'named_rule_id' : UUID }); + const NamedRuleCallerPrivileges = IDL.Record({ + 'id' : UUID, + 'can_delete' : IDL.Bool, + 'can_edit' : IDL.Bool, + }); + const GetNamedRuleResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'privileges' : NamedRuleCallerPrivileges, + 'named_rule' : NamedRule, + }), + 'Err' : Error, + }); + const SortByDirection = IDL.Variant({ 'Asc' : IDL.Null, 'Desc' : IDL.Null }); + const ListRequestsSortBy = IDL.Variant({ + 'ExpirationDt' : SortByDirection, + 'LastModificationDt' : SortByDirection, + 'CreatedAt' : SortByDirection, + }); + const ListRequestsOperationType = IDL.Variant({ + 'RemoveAsset' : IDL.Null, + 'AddUserGroup' : IDL.Null, + 'EditPermission' : IDL.Null, + 'SnapshotExternalCanister' : IDL.Opt(IDL.Principal), + 'PruneExternalCanister' : IDL.Opt(IDL.Principal), + 'EditNamedRule' : IDL.Null, + 'ConfigureExternalCanister' : IDL.Opt(IDL.Principal), + 'ChangeExternalCanister' : IDL.Opt(IDL.Principal), + 'MonitorExternalCanister' : IDL.Opt(IDL.Principal), + 'AddUser' : IDL.Null, + 'EditAsset' : IDL.Null, + 'EditUserGroup' : IDL.Null, + 'SetDisasterRecovery' : IDL.Null, + 'EditRequestPolicy' : IDL.Null, + 'RemoveRequestPolicy' : IDL.Null, + 'AddAsset' : IDL.Null, + 'SystemUpgrade' : IDL.Null, + 'RemoveAddressBookEntry' : IDL.Null, + 'SystemRestore' : IDL.Null, + 'CreateExternalCanister' : IDL.Null, + 'EditAddressBookEntry' : IDL.Null, + 'FundExternalCanister' : IDL.Opt(IDL.Principal), + 'EditUser' : IDL.Null, + 'ManageSystemInfo' : IDL.Null, + 'Transfer' : IDL.Opt(UUID), + 'EditAccount' : IDL.Null, + 'AddAddressBookEntry' : IDL.Null, + 'AddRequestPolicy' : IDL.Null, + 'RemoveNamedRule' : IDL.Null, + 'RemoveUserGroup' : IDL.Null, + 'CallExternalCanister' : IDL.Opt(IDL.Principal), + 'AddNamedRule' : IDL.Null, + 'RestoreExternalCanister' : IDL.Opt(IDL.Principal), + 'AddAccount' : IDL.Null, + }); + const GetNextApprovableRequestInput = IDL.Record({ + 'sort_by' : IDL.Opt(ListRequestsSortBy), + 'excluded_request_ids' : IDL.Vec(UUID), + 'operation_types' : IDL.Opt(IDL.Vec(ListRequestsOperationType)), + }); + const GetRequestResultData = IDL.Record({ + 'privileges' : RequestCallerPrivileges, + 'request' : Request, + 'additional_info' : RequestAdditionalInfo, + }); + const GetNextApprovableRequestResult = IDL.Variant({ + 'Ok' : IDL.Opt(GetRequestResultData), + 'Err' : Error, + }); + const GetPermissionInput = IDL.Record({ 'resource' : Resource }); + const Permission = IDL.Record({ 'resource' : Resource, 'allow' : Allow }); + const PermissionCallerPrivileges = IDL.Record({ + 'resource' : Resource, + 'can_edit' : IDL.Bool, + }); + const GetPermissionResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'permission' : Permission, + 'privileges' : PermissionCallerPrivileges, + }), + 'Err' : Error, + }); + const GetRequestInput = IDL.Record({ + 'request_id' : UUID, + 'with_full_info' : IDL.Opt(IDL.Bool), + }); + const GetRequestResult = IDL.Variant({ + 'Ok' : GetRequestResultData, + 'Err' : Error, + }); + const GetRequestPolicyInput = IDL.Record({ 'id' : UUID }); + const RequestPolicyCallerPrivileges = IDL.Record({ + 'id' : UUID, + 'can_delete' : IDL.Bool, + 'can_edit' : IDL.Bool, + }); + const RequestPolicy = IDL.Record({ + 'id' : UUID, + 'rule' : RequestPolicyRule, + 'specifier' : RequestSpecifier, + }); + const GetRequestPolicyResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'privileges' : RequestPolicyCallerPrivileges, + 'policy' : RequestPolicy, + }), + 'Err' : Error, + }); + const GetTransfersInput = IDL.Record({ 'transfer_ids' : IDL.Vec(UUID) }); + const TransferStatus = IDL.Variant({ + 'Failed' : IDL.Record({ 'reason' : IDL.Text }), + 'Processing' : IDL.Record({ 'started_at' : TimestampRFC3339 }), + 'Created' : IDL.Null, + 'Completed' : IDL.Record({ + 'signature' : IDL.Opt(IDL.Text), + 'hash' : IDL.Opt(IDL.Text), + 'completed_at' : TimestampRFC3339, + }), + }); + const Transfer = IDL.Record({ + 'id' : UUID, + 'to' : IDL.Text, + 'fee' : IDL.Nat, + 'request_id' : UUID, + 'status' : TransferStatus, + 'from_account_id' : UUID, + 'metadata' : IDL.Vec(TransferMetadata), + 'network' : Network, + 'amount' : IDL.Nat, + }); + const GetTransfersResult = IDL.Variant({ + 'Ok' : IDL.Record({ 'transfers' : IDL.Vec(Transfer) }), + 'Err' : Error, + }); + const GetUserInput = IDL.Record({ 'user_id' : UUID }); + const UserCallerPrivileges = IDL.Record({ + 'id' : UUID, + 'can_edit' : IDL.Bool, + }); + const GetUserResult = IDL.Variant({ + 'Ok' : IDL.Record({ 'privileges' : UserCallerPrivileges, 'user' : User }), + 'Err' : Error, + }); + const GetUserGroupInput = IDL.Record({ 'user_group_id' : UUID }); + const UserGroupCallerPrivileges = IDL.Record({ + 'id' : UUID, + 'can_delete' : IDL.Bool, + 'can_edit' : IDL.Bool, + }); + const GetUserGroupResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'privileges' : UserGroupCallerPrivileges, + 'user_group' : UserGroup, + }), + 'Err' : Error, + }); + const HealthStatus = IDL.Variant({ + 'Healthy' : IDL.Null, + 'Uninitialized' : IDL.Null, + }); + const HeaderField = IDL.Tuple(IDL.Text, IDL.Text); + const HttpRequest = IDL.Record({ + 'url' : IDL.Text, + 'method' : IDL.Text, + 'body' : IDL.Vec(IDL.Nat8), + 'headers' : IDL.Vec(HeaderField), + }); + const HttpResponse = IDL.Record({ + 'body' : IDL.Vec(IDL.Nat8), + 'headers' : IDL.Vec(HeaderField), + 'status_code' : IDL.Nat16, + }); + const TransferStatusType = IDL.Variant({ + 'Failed' : IDL.Null, + 'Processing' : IDL.Null, + 'Created' : IDL.Null, + 'Completed' : IDL.Null, + }); + const ListAccountTransfersInput = IDL.Record({ + 'account_id' : UUID, + 'status' : IDL.Opt(TransferStatusType), + 'to_dt' : IDL.Opt(TimestampRFC3339), + 'from_dt' : IDL.Opt(TimestampRFC3339), + }); + const TransferListItem = IDL.Record({ + 'to' : IDL.Text, + 'request_id' : UUID, + 'status' : TransferStatus, + 'created_at' : TimestampRFC3339, + 'transfer_id' : UUID, + 'amount' : IDL.Nat, + }); + const ListAccountTransfersResult = IDL.Variant({ + 'Ok' : IDL.Record({ 'transfers' : IDL.Vec(TransferListItem) }), + 'Err' : Error, + }); + const PaginationInput = IDL.Record({ + 'offset' : IDL.Opt(IDL.Nat64), + 'limit' : IDL.Opt(IDL.Nat16), + }); + const ListAccountsInput = IDL.Record({ + 'paginate' : IDL.Opt(PaginationInput), + 'search_term' : IDL.Opt(IDL.Text), + }); + const ListAccountsResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(AccountCallerPrivileges), + 'accounts' : IDL.Vec(Account), + 'next_offset' : IDL.Opt(IDL.Nat64), + }), + 'Err' : Error, + }); + const ListAddressBookEntriesInput = IDL.Record({ + 'ids' : IDL.Opt(IDL.Vec(UUID)), + 'address_formats' : IDL.Opt(IDL.Vec(IDL.Text)), + 'labels' : IDL.Opt(IDL.Vec(IDL.Text)), + 'blockchain' : IDL.Opt(IDL.Text), + 'addresses' : IDL.Opt(IDL.Vec(IDL.Text)), + 'paginate' : IDL.Opt(PaginationInput), + 'search_term' : IDL.Opt(IDL.Text), + }); + const ListAddressBookEntriesResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(AddressBookEntryCallerPrivileges), + 'address_book_entries' : IDL.Vec(AddressBookEntry), + 'next_offset' : IDL.Opt(IDL.Nat64), + }), + 'Err' : Error, + }); + const ListAssetsInput = IDL.Record({ 'paginate' : IDL.Opt(PaginationInput) }); + const ListAssetsResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(AssetCallerPrivileges), + 'assets' : IDL.Vec(Asset), + 'next_offset' : IDL.Opt(IDL.Nat64), + }), + 'Err' : Error, + }); + const ListExternalCanistersSortInput = IDL.Variant({ + 'Name' : SortByDirection, + }); + const ListExternalCanistersInput = IDL.Record({ + 'sort_by' : IDL.Opt(ListExternalCanistersSortInput), + 'states' : IDL.Opt(IDL.Vec(ExternalCanisterState)), + 'canister_ids' : IDL.Opt(IDL.Vec(IDL.Principal)), + 'labels' : IDL.Opt(IDL.Vec(IDL.Text)), + 'paginate' : IDL.Opt(PaginationInput), + }); + const ListExternalCanistersResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(ExternalCanisterCallerPrivileges), + 'canisters' : IDL.Vec(ExternalCanister), + 'next_offset' : IDL.Opt(IDL.Nat64), + }), + 'Err' : Error, + }); + const ListNamedRulesInput = IDL.Record({ + 'paginate' : IDL.Opt(PaginationInput), + }); + const ListNamedRulesResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(NamedRuleCallerPrivileges), + 'named_rules' : IDL.Vec(NamedRule), + 'next_offset' : IDL.Opt(IDL.Nat64), + }), + 'Err' : Error, + }); + const NotificationStatus = IDL.Variant({ + 'Read' : IDL.Null, + 'Sent' : IDL.Null, + }); + const NotificationTypeInput = IDL.Variant({ + 'RequestCreated' : IDL.Null, + 'SystemMessage' : IDL.Null, + }); + const ListNotificationsInput = IDL.Record({ + 'status' : IDL.Opt(NotificationStatus), + 'to_dt' : IDL.Opt(TimestampRFC3339), + 'from_dt' : IDL.Opt(TimestampRFC3339), + 'notification_type' : IDL.Opt(NotificationTypeInput), + }); + const RequestOperationType = IDL.Variant({ + 'RemoveAsset' : IDL.Null, + 'AddUserGroup' : IDL.Null, + 'EditPermission' : IDL.Null, + 'SnapshotExternalCanister' : IDL.Null, + 'PruneExternalCanister' : IDL.Null, + 'EditNamedRule' : IDL.Null, + 'ConfigureExternalCanister' : IDL.Null, + 'ChangeExternalCanister' : IDL.Null, + 'MonitorExternalCanister' : IDL.Null, + 'AddUser' : IDL.Null, + 'EditAsset' : IDL.Null, + 'EditUserGroup' : IDL.Null, + 'SetDisasterRecovery' : IDL.Null, + 'EditRequestPolicy' : IDL.Null, + 'RemoveRequestPolicy' : IDL.Null, + 'AddAsset' : IDL.Null, + 'SystemUpgrade' : IDL.Null, + 'RemoveAddressBookEntry' : IDL.Null, + 'SystemRestore' : IDL.Null, + 'CreateExternalCanister' : IDL.Null, + 'EditAddressBookEntry' : IDL.Null, + 'FundExternalCanister' : IDL.Null, + 'EditUser' : IDL.Null, + 'ManageSystemInfo' : IDL.Null, + 'Transfer' : IDL.Null, + 'EditAccount' : IDL.Null, + 'AddAddressBookEntry' : IDL.Null, + 'AddRequestPolicy' : IDL.Null, + 'RemoveNamedRule' : IDL.Null, + 'RemoveUserGroup' : IDL.Null, + 'CallExternalCanister' : IDL.Null, + 'AddNamedRule' : IDL.Null, + 'RestoreExternalCanister' : IDL.Null, + 'AddAccount' : IDL.Null, + }); + const NotificationType = IDL.Variant({ + 'RequestCreated' : IDL.Record({ + 'account_id' : IDL.Opt(UUID), + 'request_id' : UUID, + 'operation_type' : RequestOperationType, + 'user_id' : IDL.Opt(UUID), + }), + 'RequestRejected' : IDL.Record({ + 'request_id' : UUID, + 'reasons' : IDL.Opt(IDL.Vec(EvaluationSummaryReason)), + 'operation_type' : RequestOperationType, + }), + 'SystemMessage' : IDL.Null, + 'RequestFailed' : IDL.Record({ + 'request_id' : UUID, + 'operation_type' : RequestOperationType, + 'reason' : IDL.Opt(IDL.Text), + }), + }); + const Notification = IDL.Record({ + 'id' : UUID, + 'status' : NotificationStatus, + 'title' : IDL.Text, + 'created_at' : TimestampRFC3339, + 'notification_type' : NotificationType, + 'message' : IDL.Opt(IDL.Text), + 'target_user_id' : UUID, + }); + const ListNotificationsResult = IDL.Variant({ + 'Ok' : IDL.Record({ 'notifications' : IDL.Vec(Notification) }), + 'Err' : Error, + }); + const ListPermissionsInput = IDL.Record({ + 'resources' : IDL.Opt(IDL.Vec(Resource)), + 'paginate' : IDL.Opt(PaginationInput), + }); + const BasicUser = IDL.Record({ + 'id' : UUID, + 'status' : UserStatus, + 'name' : IDL.Text, + }); + const ListPermissionsResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'permissions' : IDL.Vec(Permission), + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(PermissionCallerPrivileges), + 'user_groups' : IDL.Vec(UserGroup), + 'users' : IDL.Vec(BasicUser), + 'next_offset' : IDL.Opt(IDL.Nat64), + }), + 'Err' : Error, + }); + const ListRequestPoliciesInput = PaginationInput; + const ListRequestPoliciesResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(RequestPolicyCallerPrivileges), + 'next_offset' : IDL.Opt(IDL.Nat64), + 'policies' : IDL.Vec(RequestPolicy), + }), + 'Err' : Error, + }); + const RequestStatusCode = IDL.Variant({ + 'Failed' : IDL.Null, + 'Approved' : IDL.Null, + 'Rejected' : IDL.Null, + 'Scheduled' : IDL.Null, + 'Cancelled' : IDL.Null, + 'Processing' : IDL.Null, + 'Created' : IDL.Null, + 'Completed' : IDL.Null, + }); + const ListRequestsInput = IDL.Record({ + 'sort_by' : IDL.Opt(ListRequestsSortBy), + 'deduplication_keys' : IDL.Opt(IDL.Vec(IDL.Text)), + 'with_evaluation_results' : IDL.Bool, + 'expiration_from_dt' : IDL.Opt(TimestampRFC3339), + 'tags' : IDL.Opt(IDL.Vec(IDL.Text)), + 'created_to_dt' : IDL.Opt(TimestampRFC3339), + 'statuses' : IDL.Opt(IDL.Vec(RequestStatusCode)), + 'approver_ids' : IDL.Opt(IDL.Vec(UUID)), + 'expiration_to_dt' : IDL.Opt(TimestampRFC3339), + 'paginate' : IDL.Opt(PaginationInput), + 'requester_ids' : IDL.Opt(IDL.Vec(UUID)), + 'operation_types' : IDL.Opt(IDL.Vec(ListRequestsOperationType)), + 'only_approvable' : IDL.Bool, + 'created_from_dt' : IDL.Opt(TimestampRFC3339), + }); + const ListRequestsResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(RequestCallerPrivileges), + 'requests' : IDL.Vec(Request), + 'next_offset' : IDL.Opt(IDL.Nat64), + 'additional_info' : IDL.Vec(RequestAdditionalInfo), + }), + 'Err' : Error, + }); + const ListUserGroupsInput = IDL.Record({ + 'paginate' : IDL.Opt(PaginationInput), + 'search_term' : IDL.Opt(IDL.Text), + }); + const ListUserGroupsResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(UserGroupCallerPrivileges), + 'user_groups' : IDL.Vec(UserGroup), + 'next_offset' : IDL.Opt(IDL.Nat64), + }), + 'Err' : Error, + }); + const ListUsersInput = IDL.Record({ + 'groups' : IDL.Opt(IDL.Vec(UUID)), + 'statuses' : IDL.Opt(IDL.Vec(UserStatus)), + 'paginate' : IDL.Opt(PaginationInput), + 'search_term' : IDL.Opt(IDL.Text), + }); + const ListUsersResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'total' : IDL.Nat64, + 'privileges' : IDL.Vec(UserCallerPrivileges), + 'users' : IDL.Vec(User), + 'next_offset' : IDL.Opt(IDL.Nat64), + }), + 'Err' : Error, + }); + const MarkNotificationsReadInput = IDL.Record({ + 'notification_ids' : IDL.Vec(UUID), + 'read' : IDL.Bool, + }); + const MarkNotificationReadResult = IDL.Variant({ + 'Ok' : IDL.Null, + 'Err' : Error, + }); + const UserPrivilege = IDL.Variant({ + 'AddUserGroup' : IDL.Null, + 'ListRequestPolicies' : IDL.Null, + 'ListNamedRules' : IDL.Null, + 'ListPermissions' : IDL.Null, + 'ListUserGroups' : IDL.Null, + 'AddUser' : IDL.Null, + 'ListUsers' : IDL.Null, + 'AddAsset' : IDL.Null, + 'SystemUpgrade' : IDL.Null, + 'CreateExternalCanister' : IDL.Null, + 'ListAssets' : IDL.Null, + 'ManageSystemInfo' : IDL.Null, + 'AddAddressBookEntry' : IDL.Null, + 'ListAccounts' : IDL.Null, + 'AddRequestPolicy' : IDL.Null, + 'ListAddressBookEntries' : IDL.Null, + 'ListExternalCanisters' : IDL.Null, + 'ListRequests' : IDL.Null, + 'CallAnyExternalCanister' : IDL.Null, + 'SystemInfo' : IDL.Null, + 'AddNamedRule' : IDL.Null, + 'Capabilities' : IDL.Null, + 'AddAccount' : IDL.Null, + }); + const MeResult = IDL.Variant({ + 'Ok' : IDL.Record({ 'me' : User, 'privileges' : IDL.Vec(UserPrivilege) }), + 'Err' : Error, + }); + const NotifyFailedStationUpgradeInput = IDL.Record({ 'reason' : IDL.Text }); + const NotifyFailedStationUpgradeResult = IDL.Variant({ + 'Ok' : IDL.Null, + 'Err' : Error, + }); + const SubmitRequestApprovalInput = IDL.Record({ + 'request_id' : UUID, + 'decision' : RequestApprovalStatus, + 'reason' : IDL.Opt(IDL.Text), + }); + const SubmitRequestApprovalResult = IDL.Variant({ + 'Ok' : IDL.Record({ + 'privileges' : RequestCallerPrivileges, + 'request' : Request, + 'additional_info' : RequestAdditionalInfo, + }), + 'Err' : Error, + }); + const DisasterRecovery = IDL.Record({ + 'user_group_name' : IDL.Opt(IDL.Text), + 'committee' : DisasterRecoveryCommittee, + }); + const CycleObtainStrategy = IDL.Variant({ + 'Disabled' : IDL.Null, + 'MintFromNativeToken' : IDL.Record({ + 'account_id' : UUID, + 'account_name' : IDL.Opt(IDL.Text), + }), + 'WithdrawFromCyclesLedger' : IDL.Record({ + 'account_id' : UUID, + 'account_name' : IDL.Opt(IDL.Text), + }), + }); + const SystemInfo = IDL.Record({ + 'disaster_recovery' : IDL.Opt(DisasterRecovery), + 'upgrader_cycles' : IDL.Opt(IDL.Nat64), + 'name' : IDL.Text, + 'last_upgrade_timestamp' : TimestampRFC3339, + 'raw_rand_successful' : IDL.Bool, + 'version' : IDL.Text, + 'cycles' : IDL.Nat64, + 'upgrader_id' : IDL.Principal, + 'cycle_obtain_strategy' : CycleObtainStrategy, + 'max_upgrader_backup_snapshots' : IDL.Nat64, + 'max_station_backup_snapshots' : IDL.Nat64, + }); + const SystemInfoResult = IDL.Variant({ + 'Ok' : IDL.Record({ 'system' : SystemInfo }), + 'Err' : Error, + }); + return IDL.Service({ + 'cancel_request' : IDL.Func( + [CancelRequestInput], + [CancelRequestResult], + [], + ), + 'canister_snapshots' : IDL.Func( + [CanisterSnapshotsInput], + [CanisterSnapshotsResult], + [], + ), + 'canister_status' : IDL.Func( + [CanisterStatusInput], + [CanisterStatusResponse], + [], + ), + 'capabilities' : IDL.Func([], [CapabilitiesResult], ['query']), + 'create_request' : IDL.Func( + [CreateRequestInput], + [CreateRequestResult], + [], + ), + 'fetch_account_balances' : IDL.Func( + [FetchAccountBalancesInput], + [FetchAccountBalancesResult], + [], + ), + 'get_account' : IDL.Func([GetAccountInput], [GetAccountResult], ['query']), + 'get_address_book_entry' : IDL.Func( + [GetAddressBookEntryInput], + [GetAddressBookEntryResult], + ['query'], + ), + 'get_asset' : IDL.Func([GetAssetInput], [GetAssetResult], ['query']), + 'get_external_canister' : IDL.Func( + [GetExternalCanisterInput], + [GetExternalCanisterResult], + ['query'], + ), + 'get_external_canister_filters' : IDL.Func( + [GetExternalCanisterFiltersInput], + [GetExternalCanisterFiltersResult], + ['query'], + ), + 'get_named_rule' : IDL.Func( + [GetNamedRuleInput], + [GetNamedRuleResult], + ['query'], + ), + 'get_next_approvable_request' : IDL.Func( + [GetNextApprovableRequestInput], + [GetNextApprovableRequestResult], + ['query'], + ), + 'get_permission' : IDL.Func( + [GetPermissionInput], + [GetPermissionResult], + ['query'], + ), + 'get_request' : IDL.Func([GetRequestInput], [GetRequestResult], ['query']), + 'get_request_policy' : IDL.Func( + [GetRequestPolicyInput], + [GetRequestPolicyResult], + ['query'], + ), + 'get_transfers' : IDL.Func( + [GetTransfersInput], + [GetTransfersResult], + ['query'], + ), + 'get_user' : IDL.Func([GetUserInput], [GetUserResult], ['query']), + 'get_user_group' : IDL.Func( + [GetUserGroupInput], + [GetUserGroupResult], + ['query'], + ), + 'health_status' : IDL.Func([], [HealthStatus], ['query']), + 'http_request' : IDL.Func([HttpRequest], [HttpResponse], ['query']), + 'list_account_transfers' : IDL.Func( + [ListAccountTransfersInput], + [ListAccountTransfersResult], + ['query'], + ), + 'list_accounts' : IDL.Func( + [ListAccountsInput], + [ListAccountsResult], + ['query'], + ), + 'list_address_book_entries' : IDL.Func( + [ListAddressBookEntriesInput], + [ListAddressBookEntriesResult], + ['query'], + ), + 'list_assets' : IDL.Func([ListAssetsInput], [ListAssetsResult], ['query']), + 'list_external_canisters' : IDL.Func( + [ListExternalCanistersInput], + [ListExternalCanistersResult], + ['query'], + ), + 'list_named_rules' : IDL.Func( + [ListNamedRulesInput], + [ListNamedRulesResult], + ['query'], + ), + 'list_notifications' : IDL.Func( + [ListNotificationsInput], + [ListNotificationsResult], + ['query'], + ), + 'list_permissions' : IDL.Func( + [ListPermissionsInput], + [ListPermissionsResult], + ['query'], + ), + 'list_request_policies' : IDL.Func( + [ListRequestPoliciesInput], + [ListRequestPoliciesResult], + ['query'], + ), + 'list_requests' : IDL.Func( + [ListRequestsInput], + [ListRequestsResult], + ['query'], + ), + 'list_user_groups' : IDL.Func( + [ListUserGroupsInput], + [ListUserGroupsResult], + ['query'], + ), + 'list_users' : IDL.Func([ListUsersInput], [ListUsersResult], ['query']), + 'mark_notifications_read' : IDL.Func( + [MarkNotificationsReadInput], + [MarkNotificationReadResult], + [], + ), + 'me' : IDL.Func([], [MeResult], ['query']), + 'notify_failed_station_upgrade' : IDL.Func( + [NotifyFailedStationUpgradeInput], + [NotifyFailedStationUpgradeResult], + [], + ), + 'submit_request_approval' : IDL.Func( + [SubmitRequestApprovalInput], + [SubmitRequestApprovalResult], + [], + ), + 'system_info' : IDL.Func([], [SystemInfoResult], ['query']), + }); +}; +export const init = ({ IDL }) => { + const RequestPolicyRule = IDL.Rec(); + const SystemUpgrade = IDL.Record({ 'name' : IDL.Opt(IDL.Text) }); + const UUID = IDL.Text; + const AssetMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text }); + const InitAssetInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'decimals' : IDL.Nat32, + 'standards' : IDL.Vec(IDL.Text), + 'metadata' : IDL.Vec(AssetMetadata), + 'name' : IDL.Text, + 'blockchain' : IDL.Text, + 'symbol' : IDL.Text, + }); + const AccountMetadata = IDL.Record({ 'key' : IDL.Text, 'value' : IDL.Text }); + const AccountSeed = IDL.Vec(IDL.Nat8); + const InitAccountInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'metadata' : IDL.Vec(AccountMetadata), + 'name' : IDL.Text, + 'assets' : IDL.Vec(UUID), + 'seed' : AccountSeed, + }); + const UserStatus = IDL.Variant({ + 'Inactive' : IDL.Null, + 'Active' : IDL.Null, + }); + const UserIdentityInput = IDL.Record({ 'identity' : IDL.Principal }); + const InitUserInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'status' : UserStatus, + 'groups' : IDL.Opt(IDL.Vec(UUID)), + 'name' : IDL.Text, + 'identities' : IDL.Vec(UserIdentityInput), + }); + const ResourceId = IDL.Variant({ 'Id' : UUID, 'Any' : IDL.Null }); + const RequestResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Read' : ResourceId, + }); + const NotificationResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Update' : ResourceId, + }); + const SystemResourceAction = IDL.Variant({ + 'Upgrade' : IDL.Null, + 'ManageSystemInfo' : IDL.Null, + 'SystemInfo' : IDL.Null, + 'Capabilities' : IDL.Null, + }); + const UserResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Read' : ResourceId, + 'Create' : IDL.Null, + 'Update' : ResourceId, + }); + const CanisterMethod = IDL.Record({ + 'canister_id' : IDL.Principal, + 'method_name' : IDL.Text, + }); + const ExecutionMethodResourceTarget = IDL.Variant({ + 'Any' : IDL.Null, + 'ExecutionMethod' : CanisterMethod, + }); + const ValidationMethodResourceTarget = IDL.Variant({ + 'No' : IDL.Null, + 'ValidationMethod' : CanisterMethod, + }); + const CallExternalCanisterResourceTarget = IDL.Record({ + 'execution_method' : ExecutionMethodResourceTarget, + 'validation_method' : ValidationMethodResourceTarget, + }); + const ExternalCanisterId = IDL.Variant({ + 'Any' : IDL.Null, + 'Canister' : IDL.Principal, + }); + const ExternalCanisterResourceAction = IDL.Variant({ + 'Call' : CallExternalCanisterResourceTarget, + 'Fund' : ExternalCanisterId, + 'List' : IDL.Null, + 'Read' : ExternalCanisterId, + 'Create' : IDL.Null, + 'Change' : ExternalCanisterId, + }); + const AccountResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Read' : ResourceId, + 'Create' : IDL.Null, + 'Transfer' : ResourceId, + 'Update' : ResourceId, + }); + const ResourceAction = IDL.Variant({ + 'List' : IDL.Null, + 'Read' : ResourceId, + 'Delete' : ResourceId, + 'Create' : IDL.Null, + 'Update' : ResourceId, + }); + const PermissionResourceAction = IDL.Variant({ + 'Read' : IDL.Null, + 'Update' : IDL.Null, + }); + const Resource = IDL.Variant({ + 'Request' : RequestResourceAction, + 'Notification' : NotificationResourceAction, + 'System' : SystemResourceAction, + 'User' : UserResourceAction, + 'ExternalCanister' : ExternalCanisterResourceAction, + 'Account' : AccountResourceAction, + 'AddressBook' : ResourceAction, + 'Asset' : ResourceAction, + 'NamedRule' : ResourceAction, + 'UserGroup' : ResourceAction, + 'Permission' : PermissionResourceAction, + 'RequestPolicy' : ResourceAction, + }); + const AuthScope = IDL.Variant({ + 'Authenticated' : IDL.Null, + 'Public' : IDL.Null, + 'Restricted' : IDL.Null, + }); + const Allow = IDL.Record({ + 'user_groups' : IDL.Vec(UUID), + 'auth_scope' : AuthScope, + 'users' : IDL.Vec(UUID), + }); + const InitPermissionInput = IDL.Record({ + 'resource' : Resource, + 'allow' : Allow, + }); + const UserSpecifier = IDL.Variant({ + 'Id' : IDL.Vec(UUID), + 'Any' : IDL.Null, + 'Group' : IDL.Vec(UUID), + }); + const Quorum = IDL.Record({ + 'min_approved' : IDL.Nat16, + 'approvers' : UserSpecifier, + }); + const QuorumPercentage = IDL.Record({ + 'min_approved' : IDL.Nat16, + 'approvers' : UserSpecifier, + }); + const AddressBookMetadata = IDL.Record({ + 'key' : IDL.Text, + 'value' : IDL.Text, + }); + RequestPolicyRule.fill( + IDL.Variant({ + 'Not' : RequestPolicyRule, + 'Quorum' : Quorum, + 'AllowListed' : IDL.Null, + 'QuorumPercentage' : QuorumPercentage, + 'AutoApproved' : IDL.Null, + 'AllOf' : IDL.Vec(RequestPolicyRule), + 'AnyOf' : IDL.Vec(RequestPolicyRule), + 'AllowListedByMetadata' : AddressBookMetadata, + 'NamedRule' : UUID, + }) + ); + const ResourceIds = IDL.Variant({ 'Any' : IDL.Null, 'Ids' : IDL.Vec(UUID) }); + const ResourceSpecifier = IDL.Variant({ + 'Any' : IDL.Null, + 'Resource' : Resource, + }); + const RequestSpecifier = IDL.Variant({ + 'RemoveAsset' : ResourceIds, + 'AddUserGroup' : IDL.Null, + 'EditPermission' : ResourceSpecifier, + 'EditNamedRule' : ResourceIds, + 'ChangeExternalCanister' : ExternalCanisterId, + 'AddUser' : IDL.Null, + 'EditAsset' : ResourceIds, + 'EditUserGroup' : ResourceIds, + 'SetDisasterRecovery' : IDL.Null, + 'EditRequestPolicy' : ResourceIds, + 'RemoveRequestPolicy' : ResourceIds, + 'AddAsset' : IDL.Null, + 'SystemUpgrade' : IDL.Null, + 'RemoveAddressBookEntry' : ResourceIds, + 'CreateExternalCanister' : IDL.Null, + 'EditAddressBookEntry' : ResourceIds, + 'FundExternalCanister' : ExternalCanisterId, + 'EditUser' : ResourceIds, + 'ManageSystemInfo' : IDL.Null, + 'Transfer' : ResourceIds, + 'EditAccount' : ResourceIds, + 'AddAddressBookEntry' : IDL.Null, + 'AddRequestPolicy' : IDL.Null, + 'RemoveNamedRule' : ResourceIds, + 'RemoveUserGroup' : ResourceIds, + 'CallExternalCanister' : CallExternalCanisterResourceTarget, + 'AddNamedRule' : IDL.Null, + 'AddAccount' : IDL.Null, + }); + const InitRequestPolicyInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'rule' : RequestPolicyRule, + 'specifier' : RequestSpecifier, + }); + const InitUserGroupInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'name' : IDL.Text, + }); + const InitAccountPermissionsInput = IDL.Record({ + 'configs_request_policy' : IDL.Opt(RequestPolicyRule), + 'read_permission' : Allow, + 'configs_permission' : Allow, + 'transfer_request_policy' : IDL.Opt(RequestPolicyRule), + 'transfer_permission' : Allow, + }); + const InitAccountWithPermissionsInput = IDL.Record({ + 'permissions' : InitAccountPermissionsInput, + 'account_init' : InitAccountInput, + }); + const DisasterRecoveryCommittee = IDL.Record({ + 'user_group_id' : UUID, + 'quorum' : IDL.Nat16, + }); + const InitNamedRuleInput = IDL.Record({ + 'id' : IDL.Opt(UUID), + 'name' : IDL.Text, + 'rule' : RequestPolicyRule, + 'description' : IDL.Opt(IDL.Text), + }); + const InitialConfig = IDL.Variant({ + 'WithDefaultPolicies' : IDL.Record({ + 'assets' : IDL.Vec(InitAssetInput), + 'admin_quorum' : IDL.Nat16, + 'accounts' : IDL.Vec(InitAccountInput), + 'users' : IDL.Vec(InitUserInput), + 'operator_quorum' : IDL.Nat16, + }), + 'WithAllDefaults' : IDL.Record({ + 'admin_quorum' : IDL.Nat16, + 'users' : IDL.Vec(InitUserInput), + 'operator_quorum' : IDL.Nat16, + }), + 'Complete' : IDL.Record({ + 'permissions' : IDL.Vec(InitPermissionInput), + 'assets' : IDL.Vec(InitAssetInput), + 'request_policies' : IDL.Vec(InitRequestPolicyInput), + 'user_groups' : IDL.Vec(InitUserGroupInput), + 'accounts' : IDL.Vec(InitAccountWithPermissionsInput), + 'disaster_recovery_committee' : IDL.Opt(DisasterRecoveryCommittee), + 'users' : IDL.Vec(InitUserInput), + 'named_rules' : IDL.Vec(InitNamedRuleInput), + }), + }); + const SystemUpgraderInput = IDL.Variant({ + 'Id' : IDL.Principal, + 'Deploy' : IDL.Record({ + 'initial_cycles' : IDL.Opt(IDL.Nat), + 'wasm_module' : IDL.Vec(IDL.Nat8), + }), + }); + const SystemInit = IDL.Record({ + 'name' : IDL.Text, + 'initial_config' : InitialConfig, + 'fallback_controller' : IDL.Opt(IDL.Principal), + 'upgrader' : SystemUpgraderInput, + }); + const SystemInstall = IDL.Variant({ + 'Upgrade' : SystemUpgrade, + 'Init' : SystemInit, + }); + return [IDL.Opt(SystemInstall)]; +}; diff --git a/cli/src/audit/identity.ts b/cli/src/audit/identity.ts new file mode 100644 index 000000000..92c94e5e6 --- /dev/null +++ b/cli/src/audit/identity.ts @@ -0,0 +1,82 @@ +import { Identity } from '@dfinity/agent'; +import { Ed25519KeyIdentity } from '@dfinity/identity'; +import { createPrivateKey } from 'crypto'; +import { existsSync, readFileSync } from 'fs'; +import { homedir } from 'os'; +import { join } from 'path'; + +const DFX_IDENTITY_STORE = join(homedir(), '.config/dfx/identity'); + +const candidatePemPaths = (identity: string): string[] => [ + join(DFX_IDENTITY_STORE, identity, `${identity}.pem`), + join(DFX_IDENTITY_STORE, identity, 'identity.pem'), + join(DFX_IDENTITY_STORE, identity, 'id.pem'), +]; + +/** + * Loads a dfx-managed identity from its plaintext PEM and returns an agent-js + * `Identity` suitable for signing canister requests. + * + * Encrypted (passphrase-protected) PEMs are not supported; the audit only + * needs read access to `list_*` query methods, and decrypting PKCS#8 with + * scrypt/PBKDF2 + AES would be a significant chunk of code for a read-only + * tool. Operators with an encrypted identity should create a plaintext one + * specifically for the audit: + * + * dfx identity new orbit-audit --storage-mode plaintext + */ +export const loadDfxIdentity = (name: string): Identity => { + if (!existsSync(DFX_IDENTITY_STORE)) { + throw new Error( + `dfx identity store not found at ${DFX_IDENTITY_STORE}. Install dfx or create the identity directory first.`, + ); + } + + const pemPath = candidatePemPaths(name).find(existsSync); + if (!pemPath) { + throw new Error(`No PEM file found for dfx identity '${name}'.`); + } + + const pem = readFileSync(pemPath, 'utf8'); + + if (pem.includes('ENCRYPTED PRIVATE KEY')) { + throw new Error( + `Identity '${name}' is passphrase-protected. The audit only needs read access; create a plaintext identity for it:\n` + + ` dfx identity new ${name}-audit --storage-mode plaintext\n` + + `then re-run with --identity ${name}-audit.`, + ); + } + + // Node's crypto can parse the PKCS#8 envelope; we then export the raw key + // material in JWK form and feed the seed into agent-js' Ed25519 identity. + let jwk; + try { + const key = createPrivateKey({ key: pem, format: 'pem' }); + jwk = key.export({ format: 'jwk' }); + } catch (err) { + throw new Error(`Failed to parse PEM for identity '${name}': ${(err as Error).message}`); + } + + if (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519') { + throw new Error( + `Identity '${name}' is not Ed25519 (kty=${jwk.kty}, crv=${jwk.crv}). The audit supports Ed25519 dfx identities only.`, + ); + } + + if (!jwk.d || !jwk.x) { + throw new Error(`Identity '${name}' PEM is missing key material.`); + } + + const secretKey = b64urlDecode(jwk.d); + const publicKey = b64urlDecode(jwk.x); + const combined = new Uint8Array(secretKey.length + publicKey.length); + combined.set(secretKey, 0); + combined.set(publicKey, secretKey.length); + return Ed25519KeyIdentity.fromSecretKey(combined.buffer); +}; + +const b64urlDecode = (input: string): Uint8Array => { + const pad = '='.repeat((4 - (input.length % 4)) % 4); + const base64 = (input + pad).replace(/-/g, '+').replace(/_/g, '/'); + return new Uint8Array(Buffer.from(base64, 'base64')); +}; diff --git a/cli/src/audit/index.ts b/cli/src/audit/index.ts index da5ba9efe..28d03746d 100644 --- a/cli/src/audit/index.ts +++ b/cli/src/audit/index.ts @@ -1,11 +1,11 @@ import { createCommand } from 'commander'; import { writeFileSync } from 'fs'; import { resolve } from 'path'; -import { assertReplicaIsHealthy } from '../utils'; import { assetEditPolicyWeakerThanTransfer } from './checks/asset-edit-policy-weaker-than-transfer'; import { externalCallValidationEqualsExecution } from './checks/external-call-validation-equals-execution'; import { quorumEmptyApproverSet } from './checks/quorum-empty-approver-set'; import { + buildStationActor, listAssets, listNamedRules, listPermissions, @@ -40,15 +40,15 @@ command.action(async options => { identity: options.identity, }; - await assertReplicaIsHealthy(ctx.network); + const actor = await buildStationActor(ctx); const [policies, users, userGroups, assets, namedRules, permissions] = await Promise.all([ - listRequestPolicies(ctx), - listUsers(ctx), - listUserGroups(ctx), - listAssets(ctx), - listNamedRules(ctx), - listPermissions(ctx), + listRequestPolicies(actor), + listUsers(actor), + listUserGroups(actor), + listAssets(actor), + listNamedRules(actor), + listPermissions(actor), ]); const findings: Finding[] = [ diff --git a/cli/src/audit/station.core.ts b/cli/src/audit/station.core.ts index a0559baef..812d007a1 100644 --- a/cli/src/audit/station.core.ts +++ b/cli/src/audit/station.core.ts @@ -1,4 +1,6 @@ -import { execAsync } from '../utils'; +import { ActorSubclass } from '@dfinity/agent'; +import { createStationActor } from './agent'; +import { loadDfxIdentity } from './identity'; import { Asset, NamedRule, Permission, RequestPolicy, User, UserGroup } from './types'; export interface StationContext { @@ -7,122 +9,126 @@ export interface StationContext { identity: string; } -const PAGE_SIZE = 50; +const PAGE_SIZE = 50n; -// Escapes a value for safe inclusion in a single-quoted POSIX shell argument. -// A literal `'` inside single quotes is impossible, so the standard idiom is to -// close the string, emit an escaped quote, and reopen: foo'bar -> 'foo'\''bar'. -const shq = (value: string): string => `'${value.replace(/'/g, "'\\''")}'`; - -const dfx = async (ctx: StationContext, method: string, args: string): Promise => { - // Candid arguments are always tuples — even single-arg calls need the outer `( ... )`. - // `method` and `args` come from this file (not user input); `identity` / `network` / - // `station` come from CLI flags and are shell-escaped to prevent injection. - const cmd = `dfx canister call --identity ${shq(ctx.identity)} --network ${shq(ctx.network)} --output json ${shq(ctx.station)} ${method} '(${args})'`; - const raw = await execAsync(cmd); - return JSON.parse(raw); -}; +interface OkOf { + Ok: T; +} const unwrapOk = (response: unknown, method: string): T => { if (response && typeof response === 'object' && 'Ok' in response) { - return (response as { Ok: T }).Ok; + return (response as OkOf).Ok; } throw new Error(`Station call '${method}' failed: ${JSON.stringify(response)}`); }; -const readOffset = (next_offset: unknown): number => { - if (Array.isArray(next_offset) && next_offset.length > 0) { - return Number(next_offset[0]); +const nextOffset = (next_offset: Array | []): bigint | null => + next_offset.length > 0 ? next_offset[0] : null; + +const paginated = async ( + method: string, + call: (pageArg: { offset: bigint; limit: bigint }) => Promise, + extract: (page: unknown) => { items: Item[]; next: Array | [] }, +): Promise => { + const items: Item[] = []; + let offset: bigint | null = 0n; + while (offset !== null) { + const response = await call({ offset, limit: PAGE_SIZE }); + const ok = unwrapOk(response, method); + const page = extract(ok); + items.push(...page.items); + offset = nextOffset(page.next); } - return 0; + return items; }; -export const listRequestPolicies = async (ctx: StationContext): Promise => { - const policies: RequestPolicy[] = []; - let offset = 0; - do { - const args = `record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }`; - const response = await dfx(ctx, 'list_request_policies', args); - const page = unwrapOk<{ policies: RequestPolicy[]; next_offset?: unknown }>( - response, - 'list_request_policies', - ); - policies.push(...page.policies); - offset = readOffset(page.next_offset); - } while (offset > 0); - return policies; +/** + * Builds an authenticated agent-js actor for the target station. + * + * Reused across every `list_*` call within a single audit run so we don't + * pay agent + root-key bootstrap cost per query. + */ +export const buildStationActor = async (ctx: StationContext): Promise => { + const identity = loadDfxIdentity(ctx.identity); + return createStationActor(ctx.station, ctx.network, identity); }; -export const listUsers = async (ctx: StationContext): Promise => { - const users: User[] = []; - let offset = 0; - do { - const args = `record { paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; statuses = null; groups = null; search_term = null; }`; - const response = await dfx(ctx, 'list_users', args); - const page = unwrapOk<{ users: User[]; next_offset?: unknown }>(response, 'list_users'); - users.push(...page.users); - offset = readOffset(page.next_offset); - } while (offset > 0); - return users; -}; +export const listRequestPolicies = (actor: ActorSubclass): Promise => + paginated( + 'list_request_policies', + p => actor.list_request_policies({ offset: [p.offset], limit: [p.limit] }) as Promise, + page => { + const ok = page as { policies: RequestPolicy[]; next_offset: Array | [] }; + return { items: ok.policies, next: ok.next_offset }; + }, + ); -export const listUserGroups = async (ctx: StationContext): Promise => { - const groups: UserGroup[] = []; - let offset = 0; - do { - const args = `record { paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; search_term = null; }`; - const response = await dfx(ctx, 'list_user_groups', args); - const page = unwrapOk<{ user_groups: UserGroup[]; next_offset?: unknown }>( - response, - 'list_user_groups', - ); - groups.push(...page.user_groups); - offset = readOffset(page.next_offset); - } while (offset > 0); - return groups; -}; +export const listUsers = (actor: ActorSubclass): Promise => + paginated( + 'list_users', + p => + actor.list_users({ + paginate: [{ offset: [p.offset], limit: [p.limit] }], + statuses: [], + groups: [], + search_term: [], + }) as Promise, + page => { + const ok = page as { users: User[]; next_offset: Array | [] }; + return { items: ok.users, next: ok.next_offset }; + }, + ); -export const listAssets = async (ctx: StationContext): Promise => { - const assets: Asset[] = []; - let offset = 0; - do { - const args = `record { paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; }`; - const response = await dfx(ctx, 'list_assets', args); - const page = unwrapOk<{ assets: Asset[]; next_offset?: unknown }>(response, 'list_assets'); - assets.push(...page.assets); - offset = readOffset(page.next_offset); - } while (offset > 0); - return assets; -}; +export const listUserGroups = (actor: ActorSubclass): Promise => + paginated( + 'list_user_groups', + p => + actor.list_user_groups({ + paginate: [{ offset: [p.offset], limit: [p.limit] }], + search_term: [], + }) as Promise, + page => { + const ok = page as { user_groups: UserGroup[]; next_offset: Array | [] }; + return { items: ok.user_groups, next: ok.next_offset }; + }, + ); -export const listPermissions = async (ctx: StationContext): Promise => { - const permissions: Permission[] = []; - let offset = 0; - do { - const args = `record { resources = null; paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; }`; - const response = await dfx(ctx, 'list_permissions', args); - const page = unwrapOk<{ permissions: Permission[]; next_offset?: unknown }>( - response, - 'list_permissions', - ); - permissions.push(...page.permissions); - offset = readOffset(page.next_offset); - } while (offset > 0); - return permissions; -}; +export const listAssets = (actor: ActorSubclass): Promise => + paginated( + 'list_assets', + p => + actor.list_assets({ + paginate: [{ offset: [p.offset], limit: [p.limit] }], + }) as Promise, + page => { + const ok = page as { assets: Asset[]; next_offset: Array | [] }; + return { items: ok.assets, next: ok.next_offset }; + }, + ); -export const listNamedRules = async (ctx: StationContext): Promise => { - const rules: NamedRule[] = []; - let offset = 0; - do { - const args = `record { paginate = opt record { offset = opt ${offset}; limit = opt ${PAGE_SIZE}; }; }`; - const response = await dfx(ctx, 'list_named_rules', args); - const page = unwrapOk<{ named_rules: NamedRule[]; next_offset?: unknown }>( - response, - 'list_named_rules', - ); - rules.push(...page.named_rules); - offset = readOffset(page.next_offset); - } while (offset > 0); - return rules; -}; +export const listPermissions = (actor: ActorSubclass): Promise => + paginated( + 'list_permissions', + p => + actor.list_permissions({ + resources: [], + paginate: [{ offset: [p.offset], limit: [p.limit] }], + }) as Promise, + page => { + const ok = page as { permissions: Permission[]; next_offset: Array | [] }; + return { items: ok.permissions, next: ok.next_offset }; + }, + ); + +export const listNamedRules = (actor: ActorSubclass): Promise => + paginated( + 'list_named_rules', + p => + actor.list_named_rules({ + paginate: [{ offset: [p.offset], limit: [p.limit] }], + }) as Promise, + page => { + const ok = page as { named_rules: NamedRule[]; next_offset: Array | [] }; + return { items: ok.named_rules, next: ok.next_offset }; + }, + ); From 7e7be2b0879c38b68f73b59fd3c24b485193f6cd Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 18:10:08 +0200 Subject: [PATCH 07/13] fix(cli): copy generated station.did.js into dist on build --- cli/package.json | 2 +- cli/src/audit/agent.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cli/package.json b/cli/package.json index fda473b56..4c38a6762 100644 --- a/cli/package.json +++ b/cli/package.json @@ -6,7 +6,7 @@ }, "main": "cli.js", "scripts": { - "build": "tsc", + "build": "tsc && mkdir -p dist/audit/generated && cp src/audit/generated/station.did.js dist/audit/generated/", "start": "node dist/cli.js", "expose": "pnpm link --global", "test": "vitest run", diff --git a/cli/src/audit/agent.ts b/cli/src/audit/agent.ts index e7d675282..d043b3b7a 100644 --- a/cli/src/audit/agent.ts +++ b/cli/src/audit/agent.ts @@ -2,10 +2,9 @@ import { Actor, ActorSubclass, HttpAgent, Identity } from '@dfinity/agent'; import { readFile } from 'fs/promises'; import { join } from 'path'; import { ROOT_PATH } from '../utils'; -// `idlFactory` is the runtime IDL constructor generated from `core/station/api/spec.did`. -// The file is a regenerable mirror of `apps/wallet/src/generated/station/station.did.js`. -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore - generated .js file without accompanying types in this directory +// `idlFactory` is the runtime IDL constructor generated from +// `core/station/api/spec.did`. Both the .js and its companion .d.ts live in +// `./generated/`; regenerate them with `pnpm run generate-station-types`. import { idlFactory } from './generated/station.did.js'; const MAINNET_HOSTS: Record = { From 458742247a8fc377a3d1126308f591d55cb5c47e Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 18:22:13 +0200 Subject: [PATCH 08/13] feat(cli): support icp-cli identities in audit via --identity-source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a second loader alongside the existing dfx PEM path so operators can run the audit signed as any identity registered with icp-cli, including delegation-based identities (Internet Identity / okta-style logins) that can't be exported as a self-contained PEM. - New `--identity-source dfx|icp` flag (default `dfx`). - `identity-icp.ts`: reads icp's `identity_list.json`, branches on `kind`: - `anonymous` → `AnonymousIdentity`. - `keyring` (Ed25519) → shells `icp identity export ` and parses the PEM with the same helper the dfx loader uses. - `internet-identity` → generates an Ed25519 session keypair locally, asks `icp identity delegation sign` to mint a 1-hour delegation for that session key, and assembles a `DelegationIdentity`. The session secret key never leaves the process. - `hsm` / `keyring`-Secp256k1 → unsupported with clear errors. - `identity.ts` refactored: `parseEd25519Pem` and `b64urlDecode` extracted so both loaders share the PKCS#8 parsing path. - `loadIdentity(source, name)` dispatcher used by `buildStationActor`. - README documents both sources. The 52 existing unit tests still pass — none touch the identity layer. --- cli/src/audit/README.md | 5 +- cli/src/audit/identity-icp.ts | 163 ++++++++++++++++++++++++++++++++++ cli/src/audit/identity.ts | 50 ++++++----- cli/src/audit/index.ts | 15 +++- cli/src/audit/station.core.ts | 5 +- 5 files changed, 213 insertions(+), 25 deletions(-) create mode 100644 cli/src/audit/identity-icp.ts diff --git a/cli/src/audit/README.md b/cli/src/audit/README.md index 32bb399fd..c61cd2535 100644 --- a/cli/src/audit/README.md +++ b/cli/src/audit/README.md @@ -2,7 +2,10 @@ Read-only sanity checks against an Orbit station's configuration. The command pulls live state via the station's `list_*` query methods using `agent-js`, runs a set of static checks against the configuration, and prints a severity-sorted report. Nothing is mutated — the audit is safe to run at any time, against any station the caller has read access to. -> **Identity prerequisite.** The audit signs requests with an Ed25519 dfx identity loaded from `~/.config/dfx/identity//identity.pem`. The `dfx` CLI itself does not need to be on PATH at runtime, but the identity store layout follows dfx's convention. Passphrase-protected identities aren't supported; create a plaintext one for the audit if needed (`dfx identity new orbit-audit --storage-mode plaintext`). +> **Identity sources.** The audit signs requests with either a dfx-managed PEM or an `icp-cli` identity: +> +> - `--identity-source dfx` (default) — reads `~/.config/dfx/identity//identity.pem`. The `dfx` CLI itself does not need to be on PATH at runtime, only the PEM. Passphrase-protected identities aren't supported; create a plaintext one for the audit if needed (`dfx identity new orbit-audit --storage-mode plaintext`). +> - `--identity-source icp` — reads the `icp-cli` identity store. Supports `kind: keyring` (Ed25519) via `icp identity export`, `kind: internet-identity` (e.g., II / Okta) via `icp identity delegation sign` (mints a 1-hour delegation for an ephemeral session key), and `kind: anonymous`. Requires `icp` on PATH. HSM and Secp256k1 keyring identities are not supported yet. ## Usage diff --git a/cli/src/audit/identity-icp.ts b/cli/src/audit/identity-icp.ts new file mode 100644 index 000000000..bcc4920ae --- /dev/null +++ b/cli/src/audit/identity-icp.ts @@ -0,0 +1,163 @@ +import { AnonymousIdentity, Identity } from '@dfinity/agent'; +import { DelegationChain, DelegationIdentity, Ed25519KeyIdentity } from '@dfinity/identity'; +import { spawnSync } from 'child_process'; +import { generateKeyPairSync } from 'crypto'; +import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs'; +import { homedir, tmpdir } from 'os'; +import { join } from 'path'; +import { b64urlDecode, parseEd25519Pem } from './identity'; + +const ICP_IDENTITY_STORE_PATHS = [ + // macOS — verified location used by org.dfinity.icp-cli. + join(homedir(), 'Library/Application Support/org.dfinity.icp-cli/identity'), + // Linux / XDG fallbacks for portability. + join(homedir(), '.local/share/org.dfinity.icp-cli/identity'), + join(homedir(), '.config/org.dfinity.icp-cli/identity'), +]; + +interface IcpIdentityEntry { + kind: 'keyring' | 'internet-identity' | 'anonymous' | 'hsm'; + principal?: string; + algorithm?: 'ed25519' | 'secp256k1'; +} + +interface IcpIdentityList { + v: number; + identities: Record; +} + +const findIcpStore = (): string => { + const found = ICP_IDENTITY_STORE_PATHS.find(existsSync); + if (!found) { + throw new Error( + `icp-cli identity store not found. Tried:\n` + + ICP_IDENTITY_STORE_PATHS.map(p => ` ${p}`).join('\n') + + `\nInstall icp-cli first.`, + ); + } + return found; +}; + +const readIdentityList = (): IcpIdentityList => { + const path = join(findIcpStore(), 'identity_list.json'); + if (!existsSync(path)) { + throw new Error(`icp-cli identity_list.json missing at ${path}`); + } + return JSON.parse(readFileSync(path, 'utf8')) as IcpIdentityList; +}; + +const runIcp = (args: string[]): { stdout: string; stderr: string; status: number | null } => { + const result = spawnSync('icp', args, { encoding: 'utf8' }); + if (result.error) { + throw new Error( + `Failed to invoke icp CLI: ${result.error.message}. Install icp-cli or ensure it is on PATH.`, + ); + } + return { stdout: result.stdout, stderr: result.stderr, status: result.status }; +}; + +/** + * Generates a short-lived Ed25519 session keypair and returns both the SPKI-form + * public-key PEM (consumed by `icp identity delegation sign --key-pem`) and an + * agent-js `Ed25519KeyIdentity` configured with the matching secret key. + */ +const generateSessionKey = (): { + pubKeyPem: string; + identity: Ed25519KeyIdentity; +} => { + const { publicKey, privateKey } = generateKeyPairSync('ed25519'); + const pubKeyPem = publicKey.export({ format: 'pem', type: 'spki' }) as string; + const jwk = privateKey.export({ format: 'jwk' }); + if (typeof jwk.d !== 'string' || typeof jwk.x !== 'string') { + throw new Error('Failed to extract Ed25519 session-key material from Node crypto.'); + } + const secret = b64urlDecode(jwk.d); + const pub = b64urlDecode(jwk.x); + const combined = new Uint8Array(secret.length + pub.length); + combined.set(secret, 0); + combined.set(pub, secret.length); + return { pubKeyPem, identity: Ed25519KeyIdentity.fromSecretKey(combined.buffer) }; +}; + +/** + * Loads an identity from the `icp-cli` identity store and returns an agent-js + * `Identity` suitable for signing canister calls. + * + * - `kind: anonymous` → `AnonymousIdentity`. + * - `kind: keyring` (Ed25519) → exports the PEM via `icp identity export` and + * parses it like a dfx-managed PEM. Secp256k1 keyring identities require an + * additional dependency and are rejected with a clear error today. + * - `kind: internet-identity` → generates an ephemeral Ed25519 session key, + * asks icp to sign a delegation chain for it (`icp identity delegation sign`), + * and assembles a `DelegationIdentity`. The session key never leaves this + * process. + * - `kind: hsm` → unsupported (PKCS#11 access is out of scope). + */ +export const loadIcpIdentity = (name: string): Identity => { + const list = readIdentityList(); + const entry = list.identities[name]; + if (!entry) { + const available = Object.keys(list.identities).join(', ') || '(none)'; + throw new Error(`icp identity '${name}' not found. Available: ${available}`); + } + + if (entry.kind === 'anonymous') { + return new AnonymousIdentity(); + } + + if (entry.kind === 'hsm') { + throw new Error( + `icp identity '${name}' is HSM-backed. PKCS#11 isn't supported by orbit-cli audit yet.`, + ); + } + + if (entry.kind === 'keyring') { + if (entry.algorithm && entry.algorithm !== 'ed25519') { + throw new Error( + `icp identity '${name}' uses ${entry.algorithm}. orbit-cli audit currently supports Ed25519 keyring identities only. Use --identity-source icp with an Ed25519 identity, or use --identity-source dfx with a plaintext Ed25519 identity.`, + ); + } + const { stdout, stderr, status } = runIcp(['identity', 'export', name]); + if (status !== 0) { + throw new Error(`'icp identity export ${name}' failed: ${stderr.trim()}`); + } + return parseEd25519Pem(stdout, name); + } + + if (entry.kind === 'internet-identity') { + const { pubKeyPem, identity: session } = generateSessionKey(); + const pubKeyPath = join( + tmpdir(), + `orbit-cli-session-${process.pid}-${process.hrtime.bigint()}.pem`, + ); + writeFileSync(pubKeyPath, pubKeyPem); + try { + const { stdout, stderr, status } = runIcp([ + 'identity', + 'delegation', + 'sign', + '--identity', + name, + '--key-pem', + pubKeyPath, + '--duration', + '1h', + ]); + if (status !== 0) { + throw new Error( + `'icp identity delegation sign --identity ${name}' failed: ${stderr.trim()}`, + ); + } + const chain = DelegationChain.fromJSON(stdout); + return DelegationIdentity.fromDelegation(session, chain); + } finally { + try { + unlinkSync(pubKeyPath); + } catch { + // Best-effort cleanup; ignore missing file or platform quirks. + } + } + } + + throw new Error(`Unknown icp identity kind: ${(entry as { kind: string }).kind}`); +}; diff --git a/cli/src/audit/identity.ts b/cli/src/audit/identity.ts index 92c94e5e6..5eedd8e86 100644 --- a/cli/src/audit/identity.ts +++ b/cli/src/audit/identity.ts @@ -4,6 +4,7 @@ import { createPrivateKey } from 'crypto'; import { existsSync, readFileSync } from 'fs'; import { homedir } from 'os'; import { join } from 'path'; +import { loadIcpIdentity } from './identity-icp'; const DFX_IDENTITY_STORE = join(homedir(), '.config/dfx/identity'); @@ -13,15 +14,21 @@ const candidatePemPaths = (identity: string): string[] => [ join(DFX_IDENTITY_STORE, identity, 'id.pem'), ]; +export type IdentitySource = 'dfx' | 'icp'; + /** - * Loads a dfx-managed identity from its plaintext PEM and returns an agent-js - * `Identity` suitable for signing canister requests. - * - * Encrypted (passphrase-protected) PEMs are not supported; the audit only - * needs read access to `list_*` query methods, and decrypting PKCS#8 with - * scrypt/PBKDF2 + AES would be a significant chunk of code for a read-only - * tool. Operators with an encrypted identity should create a plaintext one - * specifically for the audit: + * Loads a signing identity from either dfx's PEM store or the icp-cli identity + * store, depending on `source`. See `identity-icp.ts` for the icp variant. + */ +export const loadIdentity = (source: IdentitySource, name: string): Identity => { + if (source === 'icp') return loadIcpIdentity(name); + return loadDfxIdentity(name); +}; + +/** + * Loads a dfx-managed identity from its plaintext PEM. Encrypted PEMs are + * refused; the audit only needs read access, so create a plaintext identity + * for it: * * dfx identity new orbit-audit --storage-mode plaintext */ @@ -38,7 +45,6 @@ export const loadDfxIdentity = (name: string): Identity => { } const pem = readFileSync(pemPath, 'utf8'); - if (pem.includes('ENCRYPTED PRIVATE KEY')) { throw new Error( `Identity '${name}' is passphrase-protected. The audit only needs read access; create a plaintext identity for it:\n` + @@ -46,9 +52,14 @@ export const loadDfxIdentity = (name: string): Identity => { `then re-run with --identity ${name}-audit.`, ); } + return parseEd25519Pem(pem, name); +}; - // Node's crypto can parse the PKCS#8 envelope; we then export the raw key - // material in JWK form and feed the seed into agent-js' Ed25519 identity. +/** + * Parses an unencrypted Ed25519 PKCS#8 PEM into an `Ed25519KeyIdentity`. + * Shared between the dfx and icp identity loaders. + */ +export const parseEd25519Pem = (pem: string, name: string): Identity => { let jwk; try { const key = createPrivateKey({ key: pem, format: 'pem' }); @@ -59,23 +70,22 @@ export const loadDfxIdentity = (name: string): Identity => { if (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519') { throw new Error( - `Identity '${name}' is not Ed25519 (kty=${jwk.kty}, crv=${jwk.crv}). The audit supports Ed25519 dfx identities only.`, + `Identity '${name}' is not Ed25519 (kty=${jwk.kty}, crv=${jwk.crv}). The audit supports Ed25519 identities only.`, ); } - - if (!jwk.d || !jwk.x) { + if (typeof jwk.d !== 'string' || typeof jwk.x !== 'string') { throw new Error(`Identity '${name}' PEM is missing key material.`); } - const secretKey = b64urlDecode(jwk.d); - const publicKey = b64urlDecode(jwk.x); - const combined = new Uint8Array(secretKey.length + publicKey.length); - combined.set(secretKey, 0); - combined.set(publicKey, secretKey.length); + const secret = b64urlDecode(jwk.d); + const pub = b64urlDecode(jwk.x); + const combined = new Uint8Array(secret.length + pub.length); + combined.set(secret, 0); + combined.set(pub, secret.length); return Ed25519KeyIdentity.fromSecretKey(combined.buffer); }; -const b64urlDecode = (input: string): Uint8Array => { +export const b64urlDecode = (input: string): Uint8Array => { const pad = '='.repeat((4 - (input.length % 4)) % 4); const base64 = (input + pad).replace(/-/g, '+').replace(/_/g, '/'); return new Uint8Array(Buffer.from(base64, 'base64')); diff --git a/cli/src/audit/index.ts b/cli/src/audit/index.ts index 28d03746d..211bcc5ad 100644 --- a/cli/src/audit/index.ts +++ b/cli/src/audit/index.ts @@ -24,20 +24,31 @@ command .requiredOption('-s, --station ', 'The station canister id to audit.') .option('-n, --network ', 'The network the station lives on. Defaults to `ic`.', 'ic') .option( - '-i, --identity ', - 'The dfx identity to call the station with (needs read access to list_* methods). Defaults to `default`.', + '-i, --identity ', + "Identity to call the station with. Needs read access to the station's `list_*` methods.", 'default', ) + .option( + '--identity-source ', + 'Where to load the identity from: `dfx` reads ~/.config/dfx/identity//identity.pem; `icp` reads the icp-cli identity store (including II/delegation-based identities).', + 'dfx', + ) .option( '-o, --output ', 'Write the report to a file instead of stdout. The exit code is still set based on findings.', ); command.action(async options => { + if (options.identitySource !== 'dfx' && options.identitySource !== 'icp') { + throw new Error( + `Invalid --identity-source '${options.identitySource}'. Must be 'dfx' or 'icp'.`, + ); + } const ctx: StationContext = { station: options.station, network: options.network, identity: options.identity, + identitySource: options.identitySource, }; const actor = await buildStationActor(ctx); diff --git a/cli/src/audit/station.core.ts b/cli/src/audit/station.core.ts index 812d007a1..442d997fc 100644 --- a/cli/src/audit/station.core.ts +++ b/cli/src/audit/station.core.ts @@ -1,12 +1,13 @@ import { ActorSubclass } from '@dfinity/agent'; import { createStationActor } from './agent'; -import { loadDfxIdentity } from './identity'; +import { IdentitySource, loadIdentity } from './identity'; import { Asset, NamedRule, Permission, RequestPolicy, User, UserGroup } from './types'; export interface StationContext { station: string; network: string; identity: string; + identitySource: IdentitySource; } const PAGE_SIZE = 50n; @@ -49,7 +50,7 @@ const paginated = async ( * pay agent + root-key bootstrap cost per query. */ export const buildStationActor = async (ctx: StationContext): Promise => { - const identity = loadDfxIdentity(ctx.identity); + const identity = loadIdentity(ctx.identitySource, ctx.identity); return createStationActor(ctx.station, ctx.network, identity); }; From c77bcece3d85f0aec3ab9bf065086d52bed1e993 Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 18:24:46 +0200 Subject: [PATCH 09/13] fix(cli): pass 32-byte seed to Ed25519KeyIdentity.fromSecretKey --- cli/src/audit/identity-icp.ts | 6 +----- cli/src/audit/identity.ts | 8 +++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/cli/src/audit/identity-icp.ts b/cli/src/audit/identity-icp.ts index bcc4920ae..25b5e1d18 100644 --- a/cli/src/audit/identity-icp.ts +++ b/cli/src/audit/identity-icp.ts @@ -72,11 +72,7 @@ const generateSessionKey = (): { throw new Error('Failed to extract Ed25519 session-key material from Node crypto.'); } const secret = b64urlDecode(jwk.d); - const pub = b64urlDecode(jwk.x); - const combined = new Uint8Array(secret.length + pub.length); - combined.set(secret, 0); - combined.set(pub, secret.length); - return { pubKeyPem, identity: Ed25519KeyIdentity.fromSecretKey(combined.buffer) }; + return { pubKeyPem, identity: Ed25519KeyIdentity.fromSecretKey(secret.buffer) }; }; /** diff --git a/cli/src/audit/identity.ts b/cli/src/audit/identity.ts index 5eedd8e86..78954b0b0 100644 --- a/cli/src/audit/identity.ts +++ b/cli/src/audit/identity.ts @@ -77,12 +77,10 @@ export const parseEd25519Pem = (pem: string, name: string): Identity => { throw new Error(`Identity '${name}' PEM is missing key material.`); } + // agent-js Ed25519KeyIdentity.fromSecretKey takes the 32-byte seed only; + // the public key is derived internally. const secret = b64urlDecode(jwk.d); - const pub = b64urlDecode(jwk.x); - const combined = new Uint8Array(secret.length + pub.length); - combined.set(secret, 0); - combined.set(pub, secret.length); - return Ed25519KeyIdentity.fromSecretKey(combined.buffer); + return Ed25519KeyIdentity.fromSecretKey(secret.buffer); }; export const b64urlDecode = (input: string): Uint8Array => { From ae7982caaf33113e9b64735d7d301fbbd99610af Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 18:26:35 +0200 Subject: [PATCH 10/13] fix(cli): strip null targets from icp delegation before parsing --- cli/src/audit/identity-icp.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cli/src/audit/identity-icp.ts b/cli/src/audit/identity-icp.ts index 25b5e1d18..8123dca78 100644 --- a/cli/src/audit/identity-icp.ts +++ b/cli/src/audit/identity-icp.ts @@ -144,7 +144,20 @@ export const loadIcpIdentity = (name: string): Identity => { `'icp identity delegation sign --identity ${name}' failed: ${stderr.trim()}`, ); } - const chain = DelegationChain.fromJSON(stdout); + // icp emits `"targets": null` for unrestricted delegations; agent-js' + // `JsonnableDelegation` expects `targets` to be either absent or an + // array, never null. Strip the null fields before parsing. + const parsed = JSON.parse(stdout) as { + publicKey: string; + delegations: Array<{ + signature: string; + delegation: { pubkey: string; expiration: string; targets?: string[] | null }; + }>; + }; + parsed.delegations.forEach(d => { + if (d.delegation.targets === null) delete d.delegation.targets; + }); + const chain = DelegationChain.fromJSON(parsed); return DelegationIdentity.fromDelegation(session, chain); } finally { try { From 45fe875846a9b21d0401f0983682af185f139859 Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 18:27:22 +0200 Subject: [PATCH 11/13] fix(cli): build green - extract stripNullTargets helper --- cli/src/audit/identity-icp.ts | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/cli/src/audit/identity-icp.ts b/cli/src/audit/identity-icp.ts index 8123dca78..9baac8d7f 100644 --- a/cli/src/audit/identity-icp.ts +++ b/cli/src/audit/identity-icp.ts @@ -46,6 +46,22 @@ const readIdentityList = (): IcpIdentityList => { return JSON.parse(readFileSync(path, 'utf8')) as IcpIdentityList; }; +const stripNullTargets = (json: string): string => { + const parsed = JSON.parse(json) as Record; + const delegations = parsed.delegations; + if (Array.isArray(delegations)) { + for (const item of delegations) { + const delegation = (item as Record).delegation as + | Record + | undefined; + if (delegation && delegation.targets === null) { + delete delegation.targets; + } + } + } + return JSON.stringify(parsed); +}; + const runIcp = (args: string[]): { stdout: string; stderr: string; status: number | null } => { const result = spawnSync('icp', args, { encoding: 'utf8' }); if (result.error) { @@ -147,17 +163,7 @@ export const loadIcpIdentity = (name: string): Identity => { // icp emits `"targets": null` for unrestricted delegations; agent-js' // `JsonnableDelegation` expects `targets` to be either absent or an // array, never null. Strip the null fields before parsing. - const parsed = JSON.parse(stdout) as { - publicKey: string; - delegations: Array<{ - signature: string; - delegation: { pubkey: string; expiration: string; targets?: string[] | null }; - }>; - }; - parsed.delegations.forEach(d => { - if (d.delegation.targets === null) delete d.delegation.targets; - }); - const chain = DelegationChain.fromJSON(parsed); + const chain = DelegationChain.fromJSON(stripNullTargets(stdout)); return DelegationIdentity.fromDelegation(session, chain); } finally { try { From 2ed323113663bfa49136c9309664dc945ed64376 Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 18:41:59 +0200 Subject: [PATCH 12/13] fix(cli): accept icp 'web-auth' kind for browser-based identities --- cli/src/audit/identity-icp.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cli/src/audit/identity-icp.ts b/cli/src/audit/identity-icp.ts index 9baac8d7f..014124826 100644 --- a/cli/src/audit/identity-icp.ts +++ b/cli/src/audit/identity-icp.ts @@ -16,7 +16,10 @@ const ICP_IDENTITY_STORE_PATHS = [ ]; interface IcpIdentityEntry { - kind: 'keyring' | 'internet-identity' | 'anonymous' | 'hsm'; + // `web-auth` is the current name icp-cli uses for browser-based identities + // (II / OIDC providers via `icp identity link web`). Older versions of icp + // emitted `internet-identity` for the same shape; we accept both. + kind: 'keyring' | 'internet-identity' | 'web-auth' | 'anonymous' | 'hsm'; principal?: string; algorithm?: 'ed25519' | 'secp256k1'; } @@ -136,7 +139,7 @@ export const loadIcpIdentity = (name: string): Identity => { return parseEd25519Pem(stdout, name); } - if (entry.kind === 'internet-identity') { + if (entry.kind === 'internet-identity' || entry.kind === 'web-auth') { const { pubKeyPem, identity: session } = generateSessionKey(); const pubKeyPath = join( tmpdir(), From c9a6c1da3be287cdb3bbc89977c258506c34af74 Mon Sep 17 00:00:00 2001 From: Mario Ruci Date: Wed, 24 Jun 2026 18:45:09 +0200 Subject: [PATCH 13/13] docs(cli): document --identity-source and icp web-auth in audit README --- cli/src/audit/README.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/cli/src/audit/README.md b/cli/src/audit/README.md index c61cd2535..fdec6230e 100644 --- a/cli/src/audit/README.md +++ b/cli/src/audit/README.md @@ -5,7 +5,7 @@ Read-only sanity checks against an Orbit station's configuration. The command pu > **Identity sources.** The audit signs requests with either a dfx-managed PEM or an `icp-cli` identity: > > - `--identity-source dfx` (default) — reads `~/.config/dfx/identity//identity.pem`. The `dfx` CLI itself does not need to be on PATH at runtime, only the PEM. Passphrase-protected identities aren't supported; create a plaintext one for the audit if needed (`dfx identity new orbit-audit --storage-mode plaintext`). -> - `--identity-source icp` — reads the `icp-cli` identity store. Supports `kind: keyring` (Ed25519) via `icp identity export`, `kind: internet-identity` (e.g., II / Okta) via `icp identity delegation sign` (mints a 1-hour delegation for an ephemeral session key), and `kind: anonymous`. Requires `icp` on PATH. HSM and Secp256k1 keyring identities are not supported yet. +> - `--identity-source icp` — reads the `icp-cli` identity store. Supports `kind: keyring` (Ed25519) via `icp identity export`, `kind: web-auth` / `kind: internet-identity` (browser logins via `icp identity link web --app `) via `icp identity delegation sign` (mints a 1-hour delegation for an ephemeral session key), and `kind: anonymous`. Requires `icp` on PATH. HSM and Secp256k1 keyring identities are not supported yet. ## Usage @@ -15,13 +15,14 @@ orbit-cli audit --station [--network ] [--identity ` | **required** | The station canister id to audit. | -| `-n, --network ` | `ic` | The network the station lives on. | -| `-i, --identity ` | `default` | The dfx identity used to call the station. Must have read access to the station's `list_*` query methods (admin-tier users have this by default). | -| `-o, --output ` | _(stdout)_ | Write the report to a file instead of stdout. Exit code is still set based on findings. | -| `-h, --help` | | Print help and exit. | +| Flag | Default | Purpose | +| ----------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- | +| `-s, --station ` | **required** | The station canister id to audit. | +| `-n, --network ` | `ic` | The network the station lives on. | +| `-i, --identity ` | `default` | Identity to call the station with. Needs read access to the station's `list_*` query methods (admin-tier users have this by default). | +| `--identity-source ` | `dfx` | Where to load the identity from. `dfx` reads `~/.config/dfx/identity//identity.pem`; `icp` reads the icp-cli identity store. | +| `-o, --output ` | _(stdout)_ | Write the report to a file instead of stdout. Exit code is still set based on findings. | +| `-h, --help` | | Print help and exit. | ### Exit codes @@ -44,6 +45,11 @@ orbit-cli audit --station rrkah-fqaaa-aaaaa-aaaaq-cai \ --identity admin-readonly \ --output ./audit-report.txt +# Sign with an icp-cli identity (II / Okta / linked browser logins). +orbit-cli audit --station rrkah-fqaaa-aaaaa-aaaaq-cai \ + --identity my-icp-identity \ + --identity-source icp + # Local development station. orbit-cli audit --station --network local ```