Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 69 additions & 29 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,48 +27,88 @@ Either `environment` or both `rpcUrl` and `networkPassphrase` must be provided.

## `ComplianceModule`

Whitelist / KYC verification module. Accessed via `client.compliance`. Wraps the contract's `is_whitelisted` read-only function.
Whitelist/compliance lifecycle module. Accessed via `client.compliance`. The
current verified contract read method is `is_whitelisted`. This SDK build does
not expose a verified whitelist-write contract method, so admin mutation is
explicitly gated instead of guessing an ABI entrypoint.

See [Compliance Lifecycle Client](./compliance-lifecycle.md) for usage and
security/compliance boundaries.

### `checkWhitelist(address: string): Promise<boolean>`

Queries the contract to check if a user is KYC-approved (whitelisted).
Compatibility boolean query. Returns `true` only when the contract simulation
produces a confirmed boolean true. A confirmed false or unusable simulation
returns false.

**Signature**
```typescript
public async checkWhitelist(address: string): Promise<boolean>
```
The address is validated as a Stellar Ed25519 account before RPC work.
Invalid input throws `ComplianceLifecycleError` with
`code: "INVALID_ADDRESS"`. Thrown RPC failures pass through the client's typed
network-failure boundary.

**Parameters**
* `address` (string): The Stellar public key (`G...`) to check.
Prefer `getComplianceStatus()` when an application must distinguish a confirmed
negative result from an unavailable read.

**Returns**
`Promise<boolean>` — `true` if the simulated call to `is_whitelisted` succeeds and decodes to `true`. Resolves to `false` both when the contract reports the address is not whitelisted, *and* when the simulation does not succeed or returns no result — the current implementation does not distinguish those two cases in its return value.
### `getComplianceStatus(address: string): Promise<ComplianceStatusSnapshot>`

**Errors**
* If `simulateTransaction` itself throws (network failure, malformed request, etc.), the error is logged via `console.error` and then re-thrown as-is. It is the raw error from the underlying `@stellar/stellar-sdk` RPC call — `checkWhitelist` does not wrap it in `PortfolioError` or any other typed error.
* A failed/unsuccessful simulation that does *not* throw is swallowed and reported as `false` (see Returns above), not as an error.
Returns one of three conservative protocol states:

**Example**
```typescript
import { AegisClient } from '@aegis/sdk';
import { Networks } from '@stellar/stellar-sdk';
| Status | Code | Meaning |
| --- | --- | --- |
| `approved` | `WHITELIST_APPROVED` | Contract returned true. |
| `not-approved` | `WHITELIST_NOT_APPROVED` | Contract returned false. |
| `unavailable` | `READ_UNAVAILABLE` | No reliable boolean was observed or the read failed. |

const client = new AegisClient({
rpcUrl: 'https://soroban-testnet.stellar.org',
networkPassphrase: Networks.TESTNET,
contractId: 'C...', // Aegis Protocol Contract ID
});
`eligible` is true/false only for confirmed contract booleans and null when the
read is unavailable. Provider error details are not copied into the returned
snapshot.

try {
const isWhitelisted = await client.compliance.checkWhitelist('G_USER_PUBLIC_KEY');
console.log('Is User Whitelisted?', isWhitelisted);
} catch (error) {
// Raised on RPC/network failure during simulation — not a PortfolioError.
console.error('Whitelist check failed:', error);
### `diagnoseLifecycle(): ComplianceLifecycleDiagnostic`

Returns a serializable capability snapshot describing the verified read method,
whether a signer is configured, and the admin-write capability state. The
current admin state is:

```ts
{
supported: false,
requiresSigner: true,
reasonCode: 'CONTRACT_WRITE_METHOD_UNAVAILABLE'
}
```

> **Open note:** `checkWhitelist` passes the raw invocation object returned by `contract.call(...)` directly as the `transaction` field to `simulateTransaction` (cast through `as any`), rather than assembling a full `Transaction` via `TransactionBuilder` the way `AssetModule.mint`/`transfer` do. The source itself flags this with a comment ("Cast required depending on SDK version wrapper"), so the exact request shape expected by `simulateTransaction` across `@stellar/stellar-sdk` versions is not fully confirmed — verify against the installed SDK version rather than assuming it's stable.
The diagnostic reports `legalStatus: "not-assessed"` and never includes signer
key material.

### `updateWhitelist(address: string, approved: boolean): Promise<never>`

Explicitly gated mutation path. The method validates the address and then:

1. throws `SIGNER_REQUIRED` when no signer is configured;
2. with a signer present, throws `ADMIN_UPDATE_UNSUPPORTED` because this SDK
source does not have a verified whitelist-write contract method.

This ordering preserves the signer boundary for mutating flows without
fabricating a Soroban call. Once a contract write ABI is verified, this method
can be implemented behind the same typed surface.

Role-aware callers may also use
`createComplianceOperatorClient(...).assertWhitelistAccess()` or
`createAdminClient(...).assertWhitelistAccess()` before entering an admin flow.
Those are SDK-level capability guardrails, not on-chain authorization.

### `ComplianceLifecycleError`

Typed error codes:

- `INVALID_ADDRESS` — invalid Stellar account address;
- `SIGNER_REQUIRED` — mutation attempted without signer capability;
- `ADMIN_UPDATE_UNSUPPORTED` — no verified contract whitelist-write method is
available in this SDK build.

> Compliance state is protocol data, not legal advice or proof of KYC/AML
> completion. Contract authorization remains authoritative for protocol actions.


---

Expand Down
110 changes: 110 additions & 0 deletions docs/compliance-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Compliance Lifecycle Client

The SDK compliance lifecycle surface separates three questions that should not
be conflated:

1. What did the deployed contract report for `is_whitelisted`?
2. Was the read unavailable or malformed?
3. Can this SDK build safely submit a whitelist mutation?

The current contract-facing SDK has a verified read method,
`is_whitelisted`, but it does not expose a verified whitelist-write contract
method. The lifecycle client therefore supports typed reads and **explicitly
gates** admin mutations rather than guessing an ABI entrypoint.

## Read the current whitelist state

Use `getComplianceStatus()` when an application needs to distinguish a
confirmed negative result from an unavailable read.

```ts
const status = await client.compliance.getComplianceStatus(investorAddress);

switch (status.status) {
case 'approved':
// Contract returned true.
break;
case 'not-approved':
// Contract returned false.
break;
case 'unavailable':
// No reliable boolean was observed. Do not treat this as a denial.
break;
}
```

The legacy `checkWhitelist()` method remains boolean-compatible: only a
confirmed `true` returns true. A confirmed false or unusable simulation
returns false. New code that needs failure-state fidelity should prefer
`getComplianceStatus()`.

Both methods validate the address as a Stellar Ed25519 account before attempting
RPC work. Invalid input throws `ComplianceLifecycleError` with code
`INVALID_ADDRESS`.

## Admin whitelist updates

```ts
try {
await client.compliance.updateWhitelist(investorAddress, true);
} catch (error) {
if (error instanceof ComplianceLifecycleError) {
console.log(error.code);
}
}
```

The current behavior is deliberately gated:

- without a configured signer, the method throws `SIGNER_REQUIRED`;
- with a signer, it throws `ADMIN_UPDATE_UNSUPPORTED`.

That ordering preserves the mutating-call signer boundary while making the
contract limitation explicit. It does **not** manufacture an `add_whitelist`,
`set_whitelist`, or similarly named Soroban call that has not been verified
against the deployed contract.

Role-aware clients can additionally use
`createComplianceOperatorClient(...).assertWhitelistAccess()` or
`createAdminClient(...).assertWhitelistAccess()` to enforce their declared
SDK capability before entering an admin flow. Those role assertions are
client-side guardrails; the deployed contract remains authoritative for actual
authorization once a real write ABI exists.

## Capability diagnostic

`diagnoseLifecycle()` returns a small, serializable capability snapshot:

```ts
const diagnostic = client.compliance.diagnoseLifecycle();
```

It reports:

- read support and the verified `is_whitelisted` method;
- that admin mutation is currently unsupported and requires a signer;
- whether a signer is configured as a boolean;
- `legalStatus: "not-assessed"`.

The diagnostic never includes signer key material.

## Error model

`ComplianceLifecycleError.code` is one of:

| Code | Meaning |
| --- | --- |
| `INVALID_ADDRESS` | The target is not a valid Stellar Ed25519 account address. |
| `SIGNER_REQUIRED` | A mutating compliance flow was attempted without a signer. |
| `ADMIN_UPDATE_UNSUPPORTED` | This SDK build has no verified contract whitelist-write method. |

Expected RPC/read failures do not leak provider messages through
`getComplianceStatus()`; they resolve to the typed `unavailable` state.

## Compliance boundary

Whitelist state is protocol data, not a legal conclusion. A positive contract
read does not by itself prove KYC/AML completion, regulatory eligibility, or
permission to perform a particular RWA transaction. Applications should treat
contract authorization as authoritative for protocol actions and use qualified
compliance processes for legal/regulatory determinations.
Loading