Skip to content

Commit 974305f

Browse files
authored
feat: add bulla_registry_export for bulla-registry (#114)
* feat: add bulla_registry_export for bulla-registry Adds address_config.json, ABIs, TypeChain types, and agent skill files for the centralized bulla-registry. Exports V2 contract addresses across 10 networks plus skills for create-invoice, pay-invoice, and offer-frendlend. * feat: update skill based on agent feedback
1 parent bf2e06b commit 974305f

15 files changed

Lines changed: 7894 additions & 0 deletions

bulla_registry_export/abis/BullaApprovalRegistry.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

bulla_registry_export/abis/BullaClaimPermitLib.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

bulla_registry_export/abis/BullaClaimV2.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

bulla_registry_export/abis/BullaFrendLendV2.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

bulla_registry_export/abis/BullaInvoice.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
"repo": "bulla-contracts-v2",
3+
"contracts": {
4+
"1": {
5+
"bullaClaimV2": "0x10a55a4dbd24fa188eed98a2adae2ebff0ef1219",
6+
"bullaApprovalRegistry": "0x998d8d1ef0984fbd226eb61d97e291b2d3eb7bfb",
7+
"bullaInvoice": "0xfe2631bcb3e622750b6fbb605a416173ffa3a770",
8+
"bullaClaimPermitLib": "0x07e0b0d697419b050ba531df6db8671f14433998",
9+
"frendLendV2": "0x1097b7ecf0721aaffff147cf7bec154422896317"
10+
},
11+
"10": {
12+
"bullaClaimV2": "0x54280b44f8725c89d57d224c4d5a2f8870549471",
13+
"bullaApprovalRegistry": "0x4d5ea6f590a5bec220995f63b31fa9585dbed857",
14+
"bullaInvoice": "0x12c0edd1de5578e02390209224f22fe1ca2d0d40",
15+
"bullaClaimPermitLib": "0x07e0b0d697419b050ba531df6db8671f14433998",
16+
"frendLendV2": "0x17eb35e2205820aa4ed30704226f2517507f433b"
17+
},
18+
"56": {
19+
"bullaClaimV2": "0xbe25a1086de2b587b2d20e4b14c442cda2437945",
20+
"bullaApprovalRegistry": "0x6985d6af038f177438a6681d1f64d4409dc8aac2",
21+
"bullaInvoice": "0xba80d22b532eb1a2326334f35565af551f9c8af7",
22+
"bullaClaimPermitLib": "0x07e0b0d697419b050ba531df6db8671f14433998",
23+
"frendLendV2": "0xf34aa523a1cf4d91a3cb1c53cb50acd6eed0b153"
24+
},
25+
"100": {
26+
"bullaClaimV2": "0xf28c50eb5996508b558f0ffdde637c4385117430",
27+
"bullaApprovalRegistry": "0xb358fb00a1403fe38099dba2946631f092ea21b4",
28+
"bullaInvoice": "0xd60f8fc651bdcee2c6ed9914d610c0e22ce190d6",
29+
"bullaClaimPermitLib": "0x07e0b0d697419b050ba531df6db8671f14433998",
30+
"frendLendV2": "0x7c3e51e84705fed1832bd6aabb9edd3e4681374e"
31+
},
32+
"137": {
33+
"bullaClaimV2": "0x0313433613f24c73efc15c5c74408f40b462fd9e",
34+
"bullaApprovalRegistry": "0xf446785eb5e7ebeafd89e7a0ec1fe4ec37686486",
35+
"bullaInvoice": "0x7c2cc85cb30844b81524e703f04a5ee98e3313fb",
36+
"bullaClaimPermitLib": "0x07e0b0d697419b050ba531df6db8671f14433998",
37+
"frendLendV2": "0x8a6b12c9980249f4e52b1e2a31cdbc23d3354629"
38+
},
39+
"8453": {
40+
"bullaClaimV2": "0x8D59E594a3e4D0647C15887Cde5ECBfBE583b441",
41+
"bullaApprovalRegistry": "0x909820879c91F1EB51c44367414ccBD19Ed06B75",
42+
"bullaInvoice": "0x1E1d535a41515D3D2c29C1524C825236D67733E1",
43+
"bullaClaimPermitLib": "0x07e0b0d697419b050ba531df6db8671f14433998",
44+
"frendLendV2": "0x777A7966464a4E5684FE95025aDb2AD56bdaE77B"
45+
},
46+
"42161": {
47+
"bullaClaimV2": "0xb58f4f651553d51d95c69f59364a9ee1ca554b7e",
48+
"bullaApprovalRegistry": "0x8948a00fac01110210f97f8f9fa107aa07e6f46c",
49+
"bullaInvoice": "0x74c62f475464a03a462578d65629240b34221c1b",
50+
"bullaClaimPermitLib": "0x07e0b0d697419b050ba531df6db8671f14433998",
51+
"frendLendV2": "0x1a34dfd1ee17130228452f3d9cdda5908865d22d"
52+
},
53+
"42220": {
54+
"bullaClaimV2": "0xd60f8fc651bdcee2c6ed9914d610c0e22ce190d6",
55+
"bullaApprovalRegistry": "0x5e4fd9bb839ebea7eaf1c74edf4e92ed5c1bd773",
56+
"bullaInvoice": "0xdeb31d99d92d92988b2cce4312c807fbf4406f4f",
57+
"bullaClaimPermitLib": "0x07e0b0d697419b050ba531df6db8671f14433998",
58+
"frendLendV2": "0xcc32679a9bbfce9e49d1eba4faf42e68a227bd06"
59+
},
60+
"43114": {
61+
"bullaClaimV2": "0xf28c50eb5996508b558f0ffdde637c4385117430",
62+
"bullaApprovalRegistry": "0xb358fb00a1403fe38099dba2946631f092ea21b4",
63+
"bullaInvoice": "0xd60f8fc651bdcee2c6ed9914d610c0e22ce190d6",
64+
"bullaClaimPermitLib": "0x07e0b0d697419b050ba531df6db8671f14433998",
65+
"frendLendV2": "0x7c3e51e84705fed1832bd6aabb9edd3e4681374e"
66+
},
67+
"11155111": {
68+
"bullaClaimV2": "0x0d9EF9d436fF341E500360a6B5E5750aB85BCCB6",
69+
"bullaApprovalRegistry": "0xb1F9a06D72F8737B4fcf4550f1C8EA769772Ad76",
70+
"bullaInvoice": "0xa2c4B7239A0d179A923751cC75277fe139AB092F",
71+
"bullaClaimPermitLib": "0x334D266830326Be3Cc130C80D022eD98ea623379",
72+
"frendLendV2": "0x4d6A66D32CF34270e4cc9C9F201CA4dB650Be3f2"
73+
}
74+
}
75+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Offer FrendLend Loan
2+
3+
## Description
4+
Create a peer-to-peer loan offer using the BullaFrendLendV2 controller. Either the creditor or the debtor can initiate: if called by the creditor, it creates an offer for the debtor to accept; if called by the debtor, it creates a request for the creditor to fund.
5+
6+
## Context
7+
- **Repo**: bulla-contracts-v2
8+
- **Contract**: frendLendV2 (controller), bullaClaimV2 (underlying claim)
9+
- **Networks**: all except RedBelly (chainId 151)
10+
11+
## Prerequisites
12+
- Lender (creditor) and borrower (debtor) wallet addresses
13+
- ERC20 token address for the loan
14+
- The caller must have approved the FrendLendV2 contract on the BullaApprovalRegistry
15+
16+
## Function signature
17+
```solidity
18+
function offerLoan(LoanRequestParams calldata offer) external returns (uint256);
19+
```
20+
21+
## LoanRequestParams struct
22+
```solidity
23+
struct LoanRequestParams {
24+
uint256 termLength; // loan term in seconds
25+
InterestConfig interestConfig; // interest rate and compounding
26+
uint256 loanAmount; // principal amount in token's smallest unit
27+
address creditor; // lender address
28+
address debtor; // borrower address
29+
string description; // loan description
30+
address token; // ERC20 token address
31+
uint256 impairmentGracePeriod; // seconds after term ends before impairment allowed
32+
uint256 expiresAt; // timestamp when offer expires (0 = no expiry)
33+
address callbackContract; // contract to call when loan is accepted (address(0) = none)
34+
bytes4 callbackSelector; // function selector for callback
35+
}
36+
37+
struct InterestConfig {
38+
uint16 interestRateBps; // net interest rate in basis points (100 = 1%)
39+
uint16 numberOfPeriodsPerYear; // compounding frequency (e.g. 12 = monthly)
40+
}
41+
```
42+
43+
## Steps
44+
1. Look up `frendLendV2` and `bullaApprovalRegistry` addresses for the target chain
45+
2. Call `setApprovalForController(frendLendV2Address, true)` on BullaApprovalRegistry (one-time)
46+
3. Call `offerLoan(params)` on the FrendLendV2 contract
47+
4. The counterparty accepts by calling `acceptLoan(offerId)` — this transfers the principal and creates the claim
48+
5. The borrower repays via `payLoan(claimId, amount)` on FrendLendV2
49+
50+
## Example
51+
```solidity
52+
// Approve controller (one-time)
53+
bullaApprovalRegistry.setApprovalForController(frendLendV2Address, true);
54+
55+
// Create loan offer (as creditor/lender)
56+
LoanRequestParams memory params = LoanRequestParams({
57+
termLength: 30 days, // 2592000 seconds
58+
interestConfig: InterestConfig(500, 12), // 5% APR, compounded monthly
59+
loanAmount: 1000000, // 1 USDC
60+
creditor: msg.sender, // lender
61+
debtor: borrowerAddress, // borrower
62+
description: "30-day loan at 5% APR",
63+
token: usdcTokenAddress,
64+
impairmentGracePeriod: 7 days, // 604800 seconds
65+
expiresAt: block.timestamp + 7 days, // offer valid for 7 days
66+
callbackContract: address(0), // no callback
67+
callbackSelector: bytes4(0) // no callback
68+
});
69+
70+
uint256 offerId = frendLendV2.offerLoan(params);
71+
72+
// Borrower accepts (transfers principal from lender to borrower, creates claim)
73+
// token.approve(frendLendV2Address, amount) must be called by creditor first
74+
uint256 claimId = frendLendV2.acceptLoan(offerId);
75+
```
76+
77+
## Common Errors
78+
- Caller has not approved FrendLendV2 on BullaApprovalRegistry: reverts
79+
- Creditor and debtor are the same address: reverts
80+
- loanAmount is 0: reverts
81+
- Offer has expired (block.timestamp > expiresAt): reverts
82+
- Offer already accepted or rejected: reverts
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Pay Invoice
2+
3+
## Description
4+
Pay an existing on-chain invoice. The debtor transfers the owed token amount to the creditor through the BullaInvoice controller. Handles late fee accrual and supports partial payments.
5+
6+
## Context
7+
- **Repo**: bulla-contracts-v2
8+
- **Contract**: bullaInvoice
9+
- **Networks**: all except RedBelly (chainId 151)
10+
11+
## Prerequisites
12+
- The invoice claim ID (obtained from `InvoiceCreated` event)
13+
- The debtor must hold sufficient balance of the invoice's ERC20 token
14+
- The debtor must have approved the BullaInvoice contract to spend the token amount
15+
16+
## Function signature
17+
```solidity
18+
function payInvoice(uint256 claimId, uint256 paymentAmount) external payable;
19+
```
20+
21+
### Parameters
22+
| Name | Type | Description |
23+
|------|------|-------------|
24+
| `claimId` | `uint256` | The invoice's claim token ID |
25+
| `paymentAmount` | `uint256` | Amount to pay in the token's smallest unit. Can be partial. If late fees have accrued, payment is applied to interest first, then principal. |
26+
27+
## Steps
28+
1. Look up the `bullaInvoice` contract address for the target chain
29+
2. Call `token.approve(bullaInvoiceAddress, paymentAmount)` on the payment token
30+
3. Call `payInvoice(claimId, paymentAmount)` on the BullaInvoice contract
31+
4. The transaction transfers tokens from debtor to creditor and emits a `InvoicePayment` event
32+
5. If late fees have accrued (past due date with lateFeeConfig), payment covers interest first
33+
6. If the full principal + interest is paid, the invoice status changes to `Paid`
34+
35+
## Example
36+
```solidity
37+
// Step 1: Approve token spend
38+
usdc.approve(bullaInvoiceAddress, 1000000);
39+
40+
// Step 2: Pay the invoice (full amount)
41+
bullaInvoice.payInvoice(invoiceClaimId, 1000000);
42+
43+
// Or partial payment
44+
bullaInvoice.payInvoice(invoiceClaimId, 500000); // pay half
45+
```
46+
47+
## Common Errors
48+
- Insufficient token balance: ERC20 transfer reverts
49+
- Insufficient allowance: ERC20 transferFrom reverts
50+
- Claim does not exist or is not an invoice: reverts
51+
- Caller is not the debtor: reverts
52+
- Invoice already fully paid: reverts

0 commit comments

Comments
 (0)