@bamzzstudio/loka-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/contracts/LokaPayLedger.sol +182 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/docs/REMIX_DEPLOYMENT.md +64 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Loka SDK
|
|
2
|
+
|
|
3
|
+
Reusable SDK, ABIs, and deterministic invoice-agent helpers for Loka, a MiniPay-first merchant payment app on Celo.
|
|
4
|
+
|
|
5
|
+
## Packages
|
|
6
|
+
|
|
7
|
+
- `@bamzzstudio/loka-sdk`: Umbrella SDK.
|
|
8
|
+
- `@bamzzstudio/loka-core`: Invoice IDs, amount parsing, Celo constants.
|
|
9
|
+
- `@bamzzstudio/loka-contracts`: Ledger ABI and viem payment helpers.
|
|
10
|
+
- `@bamzzstudio/loka-agent`: Deterministic payment request agent.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @bamzzstudio/loka-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { preparePaymentRequest } from "@bamzzstudio/loka-sdk";
|
|
22
|
+
|
|
23
|
+
const result = preparePaymentRequest({
|
|
24
|
+
amount: "2.50",
|
|
25
|
+
tokenSymbol: "USDm",
|
|
26
|
+
note: "Lunch order",
|
|
27
|
+
customer: "Amina",
|
|
28
|
+
});
|
|
29
|
+
```
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
5
|
+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
6
|
+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
7
|
+
|
|
8
|
+
/// @title LokaPayLedger
|
|
9
|
+
/// @notice Merchant payment ledger for Celo native and ERC-20 stablecoin payments.
|
|
10
|
+
contract LokaPayLedger is Ownable {
|
|
11
|
+
using SafeERC20 for IERC20;
|
|
12
|
+
|
|
13
|
+
uint256 public constant BPS_DENOMINATOR = 10_000;
|
|
14
|
+
uint256 public constant MAX_FEE_BPS = 500;
|
|
15
|
+
|
|
16
|
+
address payable public treasury;
|
|
17
|
+
uint256 public feeBps;
|
|
18
|
+
uint256 public totalPayments;
|
|
19
|
+
|
|
20
|
+
struct Receipt {
|
|
21
|
+
bytes32 invoiceId;
|
|
22
|
+
address payer;
|
|
23
|
+
address merchant;
|
|
24
|
+
address token;
|
|
25
|
+
uint256 amount;
|
|
26
|
+
uint256 fee;
|
|
27
|
+
bytes32 memoHash;
|
|
28
|
+
uint64 paidAt;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
mapping(uint256 receiptId => Receipt receipt) public receipts;
|
|
32
|
+
mapping(bytes32 invoiceId => uint256 count) public invoicePaymentCount;
|
|
33
|
+
|
|
34
|
+
event LokaPayment(
|
|
35
|
+
uint256 indexed receiptId,
|
|
36
|
+
bytes32 indexed invoiceId,
|
|
37
|
+
address indexed merchant,
|
|
38
|
+
address payer,
|
|
39
|
+
address token,
|
|
40
|
+
uint256 amount,
|
|
41
|
+
uint256 fee,
|
|
42
|
+
bytes32 memoHash
|
|
43
|
+
);
|
|
44
|
+
event TreasuryUpdated(address indexed oldTreasury, address indexed newTreasury);
|
|
45
|
+
event FeeBpsUpdated(uint256 oldFeeBps, uint256 newFeeBps);
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
address initialOwner_,
|
|
49
|
+
address payable treasury_,
|
|
50
|
+
uint256 feeBps_
|
|
51
|
+
) Ownable(initialOwner_) {
|
|
52
|
+
require(initialOwner_ != address(0), "OWNER_ZERO");
|
|
53
|
+
require(feeBps_ <= MAX_FEE_BPS, "FEE_TOO_HIGH");
|
|
54
|
+
|
|
55
|
+
treasury = treasury_ == address(0) ? payable(initialOwner_) : treasury_;
|
|
56
|
+
feeBps = feeBps_;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function payNative(
|
|
60
|
+
bytes32 invoiceId,
|
|
61
|
+
address payable merchant,
|
|
62
|
+
bytes32 memoHash
|
|
63
|
+
) external payable returns (uint256 receiptId) {
|
|
64
|
+
require(msg.value > 0, "AMOUNT_ZERO");
|
|
65
|
+
require(invoiceId != bytes32(0), "INVOICE_EMPTY");
|
|
66
|
+
require(merchant != address(0), "MERCHANT_ZERO");
|
|
67
|
+
|
|
68
|
+
uint256 fee = _feeFor(msg.value);
|
|
69
|
+
uint256 payout = msg.value - fee;
|
|
70
|
+
|
|
71
|
+
receiptId = _recordPayment(
|
|
72
|
+
invoiceId,
|
|
73
|
+
msg.sender,
|
|
74
|
+
merchant,
|
|
75
|
+
address(0),
|
|
76
|
+
msg.value,
|
|
77
|
+
fee,
|
|
78
|
+
memoHash
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (fee > 0) {
|
|
82
|
+
(bool feeOk, ) = treasury.call{value: fee}("");
|
|
83
|
+
require(feeOk, "FEE_TRANSFER_FAILED");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
(bool payOk, ) = merchant.call{value: payout}("");
|
|
87
|
+
require(payOk, "PAYMENT_TRANSFER_FAILED");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function payToken(
|
|
91
|
+
bytes32 invoiceId,
|
|
92
|
+
address merchant,
|
|
93
|
+
address token,
|
|
94
|
+
uint256 amount,
|
|
95
|
+
bytes32 memoHash
|
|
96
|
+
) external returns (uint256 receiptId) {
|
|
97
|
+
require(amount > 0, "AMOUNT_ZERO");
|
|
98
|
+
require(invoiceId != bytes32(0), "INVOICE_EMPTY");
|
|
99
|
+
require(merchant != address(0), "MERCHANT_ZERO");
|
|
100
|
+
require(token != address(0), "TOKEN_ZERO");
|
|
101
|
+
|
|
102
|
+
IERC20 paymentToken = IERC20(token);
|
|
103
|
+
uint256 fee = _feeFor(amount);
|
|
104
|
+
uint256 payout = amount - fee;
|
|
105
|
+
|
|
106
|
+
paymentToken.safeTransferFrom(msg.sender, address(this), amount);
|
|
107
|
+
|
|
108
|
+
receiptId = _recordPayment(
|
|
109
|
+
invoiceId,
|
|
110
|
+
msg.sender,
|
|
111
|
+
merchant,
|
|
112
|
+
token,
|
|
113
|
+
amount,
|
|
114
|
+
fee,
|
|
115
|
+
memoHash
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (fee > 0) {
|
|
119
|
+
paymentToken.safeTransfer(treasury, fee);
|
|
120
|
+
}
|
|
121
|
+
paymentToken.safeTransfer(merchant, payout);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function setTreasury(address payable newTreasury) external onlyOwner {
|
|
125
|
+
require(newTreasury != address(0), "TREASURY_ZERO");
|
|
126
|
+
|
|
127
|
+
address oldTreasury = treasury;
|
|
128
|
+
treasury = newTreasury;
|
|
129
|
+
|
|
130
|
+
emit TreasuryUpdated(oldTreasury, newTreasury);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function setFeeBps(uint256 newFeeBps) external onlyOwner {
|
|
134
|
+
require(newFeeBps <= MAX_FEE_BPS, "FEE_TOO_HIGH");
|
|
135
|
+
|
|
136
|
+
uint256 oldFeeBps = feeBps;
|
|
137
|
+
feeBps = newFeeBps;
|
|
138
|
+
|
|
139
|
+
emit FeeBpsUpdated(oldFeeBps, newFeeBps);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function _recordPayment(
|
|
143
|
+
bytes32 invoiceId,
|
|
144
|
+
address payer,
|
|
145
|
+
address merchant,
|
|
146
|
+
address token,
|
|
147
|
+
uint256 amount,
|
|
148
|
+
uint256 fee,
|
|
149
|
+
bytes32 memoHash
|
|
150
|
+
) internal returns (uint256 receiptId) {
|
|
151
|
+
receiptId = ++totalPayments;
|
|
152
|
+
invoicePaymentCount[invoiceId] += 1;
|
|
153
|
+
|
|
154
|
+
receipts[receiptId] = Receipt({
|
|
155
|
+
invoiceId: invoiceId,
|
|
156
|
+
payer: payer,
|
|
157
|
+
merchant: merchant,
|
|
158
|
+
token: token,
|
|
159
|
+
amount: amount,
|
|
160
|
+
fee: fee,
|
|
161
|
+
memoHash: memoHash,
|
|
162
|
+
paidAt: uint64(block.timestamp)
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
emit LokaPayment(
|
|
166
|
+
receiptId,
|
|
167
|
+
invoiceId,
|
|
168
|
+
merchant,
|
|
169
|
+
payer,
|
|
170
|
+
token,
|
|
171
|
+
amount,
|
|
172
|
+
fee,
|
|
173
|
+
memoHash
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function _feeFor(uint256 amount) internal view returns (uint256) {
|
|
178
|
+
return (amount * feeBps) / BPS_DENOMINATOR;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
receive() external payable {}
|
|
182
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Remix Deployment
|
|
2
|
+
|
|
3
|
+
This guide deploys `LokaPayLedger.sol` from Remix to Celo.
|
|
4
|
+
|
|
5
|
+
## Compiler
|
|
6
|
+
|
|
7
|
+
- Solidity: `0.8.24` or newer compatible `0.8.x`
|
|
8
|
+
- Enable optimization: `200` runs
|
|
9
|
+
|
|
10
|
+
## Constructor Values
|
|
11
|
+
|
|
12
|
+
`LokaPayLedger` constructor:
|
|
13
|
+
|
|
14
|
+
```solidity
|
|
15
|
+
constructor(
|
|
16
|
+
address initialOwner_,
|
|
17
|
+
address payable treasury_,
|
|
18
|
+
uint256 feeBps_
|
|
19
|
+
)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Recommended values:
|
|
23
|
+
|
|
24
|
+
- `initialOwner_`: owner wallet that can update fee and treasury
|
|
25
|
+
- `treasury_`: wallet that receives protocol fees, or zero address to use `initialOwner_`
|
|
26
|
+
- `feeBps_`: fee in basis points
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
|
|
30
|
+
- `0`: no protocol fee
|
|
31
|
+
- `30`: 0.30%
|
|
32
|
+
- `50`: 0.50%
|
|
33
|
+
|
|
34
|
+
The contract caps fees at `500` basis points, which is 5%.
|
|
35
|
+
|
|
36
|
+
## After Deployment
|
|
37
|
+
|
|
38
|
+
Add the deployed contract address to the app:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
NEXT_PUBLIC_LOKA_LEDGER_ADDRESS=0xDDBc0b6fB1fB0AAaE4321d69B3625ba4CaB2a952
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then add the same contract address to the project profile on Talent App.
|
|
45
|
+
|
|
46
|
+
Current Celo Mainnet ledger:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
0xDDBc0b6fB1fB0AAaE4321d69B3625ba4CaB2a952
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Mainnet Defaults
|
|
53
|
+
|
|
54
|
+
Celo Mainnet:
|
|
55
|
+
|
|
56
|
+
- Chain ID: `42220`
|
|
57
|
+
- RPC: `https://forno.celo.org`
|
|
58
|
+
- Explorer: `https://celoscan.io`
|
|
59
|
+
|
|
60
|
+
USDm on Celo Mainnet:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
0x765DE816845861e75A25fCA122bb6898B8B1282a
|
|
64
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bamzzstudio/loka-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SDK, contracts, and invoice agent helpers for Loka merchant payments on Celo.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"contracts",
|
|
11
|
+
"docs",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/adekunlebamz/localpay-sdk.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/adekunlebamz/localpay-sdk/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/adekunlebamz/localpay-sdk#readme",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "npm run build:packages && tsc -p tsconfig.json",
|
|
25
|
+
"build:packages": "npm run build --prefix packages/core && npm run build --prefix packages/contracts && npm run build --prefix packages/agent",
|
|
26
|
+
"typecheck": "npm run typecheck:packages && tsc -p tsconfig.json --noEmit",
|
|
27
|
+
"typecheck:packages": "npm run typecheck --prefix packages/core && npm run typecheck --prefix packages/contracts && npm run typecheck --prefix packages/agent"
|
|
28
|
+
},
|
|
29
|
+
"workspaces": [
|
|
30
|
+
"packages/core",
|
|
31
|
+
"packages/contracts",
|
|
32
|
+
"packages/agent"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@bamzzstudio/loka-agent": "^0.1.0",
|
|
36
|
+
"@bamzzstudio/loka-contracts": "^0.1.0",
|
|
37
|
+
"@bamzzstudio/loka-core": "^0.1.0",
|
|
38
|
+
"viem": "^2.48.8"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
}
|
|
43
|
+
}
|