Skip to content
Closed
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
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ and the machine-readable interface lives in
| Entrypoint | Mutates state | Purpose |
| --- | --- | --- |
| `create_vault` | Yes | Creates and funds a new vault, assigns a sequential vault id, and starts it in `Active`. |
| `validate_milestone` | Yes | Marks an `Active` vault milestone as validated before the deadline. |
| `validate_milestone` | Yes | Marks one indexed milestone on an `Active` vault as validated before the deadline. |
| `release_funds` | Yes | Sends funds to `success_destination` and moves the vault to `Completed`. |
| `redirect_funds` | Yes | Sends funds to `failure_destination` and moves the vault to `Failed`. |
| `cancel_vault` | Yes | Returns funds to the creator and moves the vault to `Cancelled`. |
Expand All @@ -40,6 +40,7 @@ and the backend mapping in [`src/doc.md`](src/doc.md#error-handling).
| `7` | `InvalidAmount` | `create_vault` | `amount` is below `MIN_AMOUNT` or above `MAX_AMOUNT`. This covers zero, negative, and over-maximum amounts. |
| `8` | `InvalidTimestamps` | `create_vault` | `end_timestamp` is less than or equal to `start_timestamp`. |
| `9` | `DurationTooLong` | `create_vault` | `end_timestamp - start_timestamp` exceeds `MAX_VAULT_DURATION` (365 days). |
| `10` | `MilestoneIndexOutOfRange` | `create_vault`, `validate_milestone` | `create_vault` receives an empty milestone vector, or `validate_milestone` receives an index outside the vault's milestone list. |

## Vault Lifecycle

Expand All @@ -49,8 +50,8 @@ Vault records are never deleted by normal contract operation. A vault starts in
```mermaid
stateDiagram-v2
[*] --> Active: create_vault
Active --> Active: validate_milestone / milestone_validated = true
Active --> Completed: release_funds / success_destination receives funds
Active --> Active: validate_milestone(index)
Active --> Completed: release_funds / all milestones validated or deadline reached
Active --> Failed: redirect_funds / failure_destination receives funds
Active --> Cancelled: cancel_vault / creator receives refund
Completed --> [*]
Expand All @@ -61,14 +62,17 @@ stateDiagram-v2
| From | To | Entrypoint | Preconditions | Resulting event |
| --- | --- | --- | --- | --- |
| None | `Active` | `create_vault` | Creator authorizes; amount and timestamps are valid; duration is within the maximum; token transfer into the contract succeeds. | `vault_created` |
| `Active` | `Active` | `validate_milestone` | Vault exists, is active, caller is the configured verifier or creator fallback, and ledger time is before `end_timestamp`. | `milestone_validated` |
| `Active` | `Completed` | `release_funds` | Creator authorizes; vault is active; milestone is validated or the deadline has been reached. | `funds_released` |
| `Active` | `Failed` | `redirect_funds` | Vault is active; ledger time is strictly greater than `end_timestamp`; milestone is not validated. | `funds_redirected` |
| `Active` | `Active` | `validate_milestone` | Vault exists, is active, caller is the configured verifier or creator fallback, ledger time is before `end_timestamp`, and the milestone index is in range and not already validated. | `milestone_validated` |
| `Active` | `Completed` | `release_funds` | Creator authorizes; vault is active; every milestone is validated or the deadline has been reached. | `funds_released` |
| `Active` | `Failed` | `redirect_funds` | Vault is active; ledger time is strictly greater than `end_timestamp`; not every milestone is validated. | `funds_redirected` |
| `Active` | `Cancelled` | `cancel_vault` | Creator authorizes and vault is active. | `vault_cancelled` |

Any attempt to call `validate_milestone`, `release_funds`, `redirect_funds`, or
`cancel_vault` after a terminal transition returns `VaultNotActive` (`#3`).

See [`docs/MULTI_MILESTONE.md`](docs/MULTI_MILESTONE.md) for the milestone
vector model and indexed validation examples.

## Source Of Truth

- [`src/lib.rs`](src/lib.rs) defines the enum values, transition guards, events,
Expand Down
14 changes: 8 additions & 6 deletions contract-interface.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
{ "name": "amount", "type": "i128" },
{ "name": "start_timestamp", "type": "u64" },
{ "name": "end_timestamp", "type": "u64" },
{ "name": "milestone_hash", "type": "BytesN<32>" },
{ "name": "milestones", "type": "Vec<BytesN<32>>" },
{ "name": "verifier", "type": "Option<Address>" },
{ "name": "success_destination", "type": "Address" },
{ "name": "failure_destination", "type": "Address" },
{ "name": "status", "type": "VaultStatus" },
{ "name": "milestone_validated", "type": "bool" }
{ "name": "milestone_validations", "type": "Vec<bool>" }
]
},
"Error": {
Expand All @@ -38,7 +38,8 @@
"InvalidStatus": 6,
"InvalidAmount": 7,
"InvalidTimestamps": 8,
"DurationTooLong": 9
"DurationTooLong": 9,
"MilestoneIndexOutOfRange": 10
}
}
},
Expand All @@ -51,7 +52,7 @@
{ "name": "amount", "type": "i128" },
{ "name": "start_timestamp", "type": "u64" },
{ "name": "end_timestamp", "type": "u64" },
{ "name": "milestone_hash", "type": "BytesN<32>" },
{ "name": "milestones", "type": "Vec<BytesN<32>>" },
{ "name": "verifier", "type": "Option<Address>" },
{ "name": "success_destination", "type": "Address" },
{ "name": "failure_destination", "type": "Address" }
Expand All @@ -61,7 +62,8 @@
{
"name": "validate_milestone",
"inputs": [
{ "name": "vault_id", "type": "u32" }
{ "name": "vault_id", "type": "u32" },
{ "name": "milestone_index", "type": "u32" }
],
"outputs": { "type": "Result<bool, Error>" }
},
Expand Down Expand Up @@ -111,7 +113,7 @@
{
"name": "milestone_validated",
"topic": ["milestone_validated", "u32"],
"data": null
"data": "u32"
},
{
"name": "funds_released",
Expand Down
29 changes: 29 additions & 0 deletions docs/MULTI_MILESTONE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Multi-Milestone Vaults

Vaults store an ordered `milestones: Vec<BytesN<32>>` plus a parallel
`milestone_validations: Vec<bool>`. Both vectors have the same length and index
`0` preserves the previous single-milestone behavior when callers submit a
one-item vector.

## Validation

`validate_milestone(vault_id, milestone_index)` marks exactly one milestone as
validated. The verifier authorization model is unchanged: a configured verifier
must authorize validation, and creator authorization is used when no verifier is
set.

The contract rejects:

- empty milestone vectors at creation
- out-of-range milestone indexes
- duplicate validation of the same milestone index
- validation at or after `end_timestamp`

## Release And Redirect

Before the deadline, `release_funds` requires every milestone validation flag to
be `true`. After the deadline, the existing deadline release path still applies.

`redirect_funds` remains available only after the deadline and only while not
every milestone has been validated, so a partially completed vault can still be
routed to `failure_destination` after the window closes.
57 changes: 33 additions & 24 deletions src/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ This guide provides comprehensive documentation for backend developers integrati
"amount": "1000000000",
"start_timestamp": 1704067200,
"end_timestamp": 1706640000,
"milestone_hash": "4d696c6573746f6e655f726571756972656d656e74735f68617368",
"milestones": [
"4d696c6573746f6e655f726571756972656d656e74735f68617368"
],
"verifier": "GB7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"success_destination": "GC7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"failure_destination": "GD7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Expand All @@ -63,7 +65,7 @@ This guide provides comprehensive documentation for backend developers integrati
| `amount` | string | Yes | Amount in stroops (7 decimals: 1 USDC = 10,000,000 stroops) |
| `start_timestamp` | integer | Yes | Unix timestamp when vault becomes active |
| `end_timestamp` | integer | Yes | Unix timestamp deadline for milestone validation |
| `milestone_hash` | string | Yes | Hex-encoded SHA-256 hash of milestone document |
| `milestones` | array<string> | Yes | Ordered milestone document hashes; submit one item to preserve the legacy single-milestone flow |
| `verifier` | string | Optional | Designated verifier address (null for creator-only validation) |
| `success_destination` | string | Yes | Address to receive funds on successful milestone |
| `failure_destination` | string | Yes | Address to receive funds on failure |
Expand Down Expand Up @@ -104,18 +106,20 @@ This guide provides comprehensive documentation for backend developers integrati

**Request Payload:**
```json
{
"vault_id": 42,
"verifier_signature": "signature_data_here"
}
{
"vault_id": 42,
"milestone_index": 0,
"verifier_signature": "signature_data_here"
}
```

**Field Descriptions:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `vault_id` | integer | Yes | ID of vault to validate |
| `verifier_signature` | string | Yes | Signed transaction from authorized verifier |
|-------|------|----------|-------------|
| `vault_id` | integer | Yes | ID of vault to validate |
| `milestone_index` | integer | Yes | Zero-based milestone index to validate |
| `verifier_signature` | string | Yes | Signed transaction from authorized verifier |

**Constraints:**
- Vault must exist and be in `Active` status
Expand All @@ -127,7 +131,8 @@ This guide provides comprehensive documentation for backend developers integrati
```json
{
"vault_id": 42,
"milestone_validated": true,
"milestone_index": 0,
"milestone_validated": true,
"transaction_hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
"ledger_sequence": 12345680,
"validated_at": "2024-01-15T12:30:00Z"
Expand Down Expand Up @@ -168,7 +173,7 @@ This guide provides comprehensive documentation for backend developers integrati

**Constraints:**
- Vault must exist and be in `Active` status
- Either `milestone_validated` is true OR current time >= `end_timestamp`
- Every milestone validation flag is true OR current time >= `end_timestamp`

**Response (200 OK):**
```json
Expand Down Expand Up @@ -217,7 +222,7 @@ This guide provides comprehensive documentation for backend developers integrati
**Constraints:**
- Vault must exist and be in `Active` status
- Current timestamp must be strictly greater than or equal to `end_timestamp`
- `milestone_validated` must be false
- At least one milestone validation flag must be false

**Response (200 OK):**
```json
Expand Down Expand Up @@ -305,12 +310,14 @@ This guide provides comprehensive documentation for backend developers integrati
"amount": "1000000000",
"start_timestamp": 1704067200,
"end_timestamp": 1706640000,
"milestone_hash": "4d696c6573746f6e655f726571756972656d656e74735f68617368",
"milestones": [
"4d696c6573746f6e655f726571756972656d656e74735f68617368"
],
"verifier": "GB7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"success_destination": "GC7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"failure_destination": "GD7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"status": "Active",
"milestone_validated": false
"milestone_validations": [false]
}
}
```
Expand Down Expand Up @@ -353,7 +360,7 @@ interface CreateVaultRequest {
amount: string;
start_timestamp: number;
end_timestamp: number;
milestone_hash: string;
milestones: string[];
verifier?: string;
success_destination: string;
failure_destination: string;
Expand Down Expand Up @@ -414,10 +421,11 @@ class VaultService {
### Pattern 2: Milestone Validation Flow

```typescript
interface ValidateMilestoneRequest {
vault_id: number;
verifier_signature: string;
}
interface ValidateMilestoneRequest {
vault_id: number;
milestone_index: number;
verifier_signature: string;
}

class ValidationService {
async validateMilestone(request: ValidateMilestoneRequest): Promise<ValidationResponse> {
Expand Down Expand Up @@ -446,7 +454,8 @@ class ValidationService {

return {
vault_id: request.vault_id,
milestone_validated: true,
milestone_index: request.milestone_index,
milestone_validated: true,
transaction_hash: result.txHash,
ledger_sequence: result.ledger,
validated_at: new Date().toISOString()
Expand Down Expand Up @@ -481,7 +490,7 @@ class ReleaseService {
// 2. Check release conditions
const now = Math.floor(Date.now() / 1000);
const deadlineReached = now >= vault.end_timestamp;
const validated = vault.milestone_validated;
const validated = vault.milestone_validations.every(Boolean);

if (!validated && !deadlineReached) {
throw new AuthorizationError('NotAuthorized',
Expand Down Expand Up @@ -588,8 +597,8 @@ All transactions benefit from Stellar's built-in replay protection via sequence
|-----------|-------------------|-------|
| `create_vault` | Creator | Must sign and authorize USDC transfer |
| `validate_milestone` | Verifier (if set) or Creator | Must be before deadline |
| `release_funds` | Anyone | Conditions: validated OR past deadline |
| `redirect_funds` | Anyone | Conditions: not validated AND past deadline |
| `release_funds` | Anyone | Conditions: all milestones validated OR past deadline |
| `redirect_funds` | Anyone | Conditions: not all milestones validated AND past deadline |
| `cancel_vault` | Creator only | Vault must be Active |

---
Expand All @@ -601,7 +610,7 @@ All transactions benefit from Stellar's built-in replay protection via sequence
| Event | Topic | Data | Trigger |
|-------|-------|------|---------|
| `vault_created` | `("vault_created", vault_id)` | `ProductivityVault` | Vault creation |
| `milestone_validated` | `("milestone_validated", vault_id)` | `()` | Successful validation |
| `milestone_validated` | `("milestone_validated", vault_id)` | `u32` | Successful validation index |
| `funds_released` | `("funds_released", vault_id)` | `amount: i128` | Fund release |
| `funds_redirected` | `("funds_redirected", vault_id)` | `amount: i128` | Fund redirect |
| `vault_cancelled` | `("vault_cancelled", vault_id)` | `()` | Vault cancellation |
Expand Down
Loading
Loading