|
| 1 | +# Create Invoice |
| 2 | + |
| 3 | +## Description |
| 4 | + |
| 5 | +Create an on-chain invoice using the BullaInvoice controller contract. An invoice is a specialized claim with additional features like late fees, impairment grace periods, delivery dates, and purchase order deposits. |
| 6 | + |
| 7 | +## Context |
| 8 | + |
| 9 | +- **Repo**: bulla-contracts-v2 |
| 10 | +- **Contract**: bullaInvoice (controller), bullaClaimV2 (underlying claim), bullaApprovalRegistry (approval) |
| 11 | +- **Networks**: all except RedBelly (chainId 151) |
| 12 | + |
| 13 | +## Creating an Invoice (Two Steps Required) |
| 14 | + |
| 15 | +### Step 1: Approve on BullaApprovalRegistry |
| 16 | + |
| 17 | +Before creating invoices, the caller must approve the BullaInvoice contract on the **BullaApprovalRegistry**. This is a separate contract — do NOT try to approve on BullaInvoice directly. |
| 18 | + |
| 19 | +```solidity |
| 20 | +function approveCreateClaim( |
| 21 | + address controller, |
| 22 | + CreateClaimApprovalType approvalType, |
| 23 | + uint64 approvalCount, |
| 24 | + bool isBindingAllowed |
| 25 | +) external; |
| 26 | +``` |
| 27 | + |
| 28 | +**Parameters:** |
| 29 | + |
| 30 | +- `controller`: BullaInvoice contract address (look up from address_config.json for target chain) |
| 31 | +- `approvalType`: `CreateClaimApprovalType` enum (see below) |
| 32 | +- `approvalCount`: Number of claims allowed (`type(uint64).max` = `18446744073709551615` for unlimited). Note: this is `uint64`, NOT `uint256` |
| 33 | +- `isBindingAllowed`: `true` to allow creating bound claims, `false` otherwise |
| 34 | + |
| 35 | +**CreateClaimApprovalType enum:** |
| 36 | +| Value | Name | Description | |
| 37 | +|-------|------|-------------| |
| 38 | +| 0 | `Unapproved` | Revoke approval (approvalCount and isBindingAllowed must be 0/false) | |
| 39 | +| 1 | `CreditorOnly` | Caller can only create claims where they are the creditor | |
| 40 | +| 2 | `DebtorOnly` | Caller can only create claims where they are the debtor | |
| 41 | +| 3 | `Approved` | Caller can create any kind of claim | |
| 42 | + |
| 43 | +**Example — unlimited approval:** |
| 44 | + |
| 45 | +```solidity |
| 46 | +bullaApprovalRegistry.approveCreateClaim( |
| 47 | + bullaInvoiceAddress, // controller |
| 48 | + CreateClaimApprovalType.Approved, // 3 — can create any kind of claim |
| 49 | + type(uint64).max, // 18446744073709551615 — unlimited |
| 50 | + true // allow binding |
| 51 | +); |
| 52 | +``` |
| 53 | + |
| 54 | +### Step 2: Create the Invoice |
| 55 | + |
| 56 | +```solidity |
| 57 | +function createInvoice(CreateInvoiceParams memory params) external payable returns (uint256); |
| 58 | +``` |
| 59 | + |
| 60 | +## CreateInvoiceParams struct |
| 61 | + |
| 62 | +```solidity |
| 63 | +struct CreateInvoiceParams { |
| 64 | + address debtor; // who pays |
| 65 | + address creditor; // who gets paid |
| 66 | + uint256 claimAmount; // amount in token's smallest unit |
| 67 | + uint256 dueBy; // due date as unix timestamp |
| 68 | + uint256 deliveryDate; // expected delivery date (0 if not applicable) |
| 69 | + string description; // invoice description |
| 70 | + address token; // ERC20 token address |
| 71 | + ClaimBinding binding; // 0 = Unbound, 1 = BindingPending, 2 = Bound |
| 72 | + InterestConfig lateFeeConfig; // late fee configuration |
| 73 | + uint256 impairmentGracePeriod; // seconds after due date before impairment allowed |
| 74 | + uint256 depositAmount; // upfront deposit required (0 if none) |
| 75 | +} |
| 76 | +
|
| 77 | +struct InterestConfig { |
| 78 | + uint16 interestRateBps; // net interest rate in basis points (100 = 1%) |
| 79 | + uint16 numberOfPeriodsPerYear; // compounding frequency (e.g. 12 = monthly) |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +## BullaApprovalRegistry ABI (approval step) |
| 84 | + |
| 85 | +```json |
| 86 | +[ |
| 87 | + "function approveCreateClaim(address controller, uint8 approvalType, uint64 approvalCount, bool isBindingAllowed)", |
| 88 | + "function getApprovals(address user, address controller) view returns (uint8 approvalType, uint64 approvalCount, bool isBindingAllowed)" |
| 89 | +] |
| 90 | +``` |
| 91 | + |
| 92 | +## Steps |
| 93 | + |
| 94 | +1. Look up `bullaInvoice` and `bullaApprovalRegistry` addresses for the target chain from `address_config.json` |
| 95 | +2. Check if approval exists: call `getApprovals(callerAddress, bullaInvoiceAddress)` on BullaApprovalRegistry |
| 96 | +3. If not approved, call `approveCreateClaim(bullaInvoiceAddress, approvalType, approvalCount, isBindingAllowed)` on BullaApprovalRegistry |
| 97 | +4. Call `createInvoice(params)` on the BullaInvoice contract |
| 98 | +5. The transaction creates a claim on BullaClaimV2 and emits an `InvoiceCreated` event |
| 99 | +6. Returns the claim token ID |
| 100 | + |
| 101 | +## Example |
| 102 | + |
| 103 | +```solidity |
| 104 | +// Step 1: Approve the invoice controller on BullaApprovalRegistry (one-time) |
| 105 | +bullaApprovalRegistry.approveCreateClaim( |
| 106 | + bullaInvoiceAddress, |
| 107 | + CreateClaimApprovalType.Approved, // 3 |
| 108 | + type(uint64).max, // unlimited |
| 109 | + true // allow binding |
| 110 | +); |
| 111 | +
|
| 112 | +// Step 2: Create the invoice |
| 113 | +CreateInvoiceParams memory params = CreateInvoiceParams({ |
| 114 | + debtor: debtorAddress, |
| 115 | + creditor: msg.sender, |
| 116 | + claimAmount: 1000000, // 1 USDC |
| 117 | + dueBy: 1709251200, // due date |
| 118 | + deliveryDate: 0, // no delivery date |
| 119 | + description: "Invoice #1234", |
| 120 | + token: usdcTokenAddress, |
| 121 | + binding: ClaimBinding.Unbound, // 0 |
| 122 | + lateFeeConfig: InterestConfig(0, 0), // no late fees |
| 123 | + impairmentGracePeriod: 0, |
| 124 | + depositAmount: 0 // no deposit |
| 125 | +}); |
| 126 | +
|
| 127 | +uint256 claimId = bullaInvoice.createInvoice(params); |
| 128 | +``` |
| 129 | + |
| 130 | +## Common Gotchas |
| 131 | + |
| 132 | +- Do NOT try to approve on BullaInvoice directly — approval happens on BullaApprovalRegistry (a separate contract) |
| 133 | +- `approvalCount` is `uint64`, not `uint256` (max value: `18446744073709551615`) |
| 134 | + |
| 135 | +## Common Errors |
| 136 | + |
| 137 | +- `NotApproved`: Caller has not approved the BullaInvoice controller on BullaApprovalRegistry, or approval type doesn't match the claim role |
| 138 | +- `InvalidApproval`: Trying to set `Unapproved` (0) with non-zero approvalCount or isBindingAllowed=true |
| 139 | +- Debtor is zero address: reverts |
| 140 | +- claimAmount is 0: reverts |
| 141 | +- Token is not a valid ERC20: reverts |
0 commit comments