Skip to content
Merged
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
6 changes: 6 additions & 0 deletions Deployments.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ as well as the constructor parameters that have been used.
| Polygon | 137 | [v1](https://github.com/StabilityNexus/Chainvoice/releases/tag/v1) | `0xD044A85a5daC307217B9bF313A90E8a60AF7DdCe` | None — constructor takes no arguments (`owner = msg.sender`, `fee` hardcoded to `0.0005 ether`) | Mainnet |
| Ethereum Sepolia | 11155111 | [v1](https://github.com/StabilityNexus/Chainvoice/releases/tag/v1) | `0x54a542dCDC306eE281b5De4613EcEfe6e6ABc562` | None — constructor takes no arguments (`owner = msg.sender`, `fee` hardcoded to `0.0005 ether`) | Testnet |

> ⚠️ **No deployment currently matches the contract in this repo.** The key
> registry functions were renamed (`registerPublicKey` / `getPublicKey`), which
> changed their selectors, so every address below is v1 only and incompatible
> with the current ABI. A fresh deployment is required, and its registry starts
> empty — every user must register their public key again.

---
**Note to Developers:** After making a new deployment, please:
1. create a git tag for the deployed version;
Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ npm run dev

### Frontend Configuration (`frontend/.env`)
```.env
#Ethereum Sepolia (11155111)
VITE_CONTRACT_ADDRESS_11155111=0x7bC4C5abb5b1B8355Aa65307C1cFDbe6254505d2
#Ethereum Sepolia (11155111) — blank until redeployed, see note below
VITE_CONTRACT_ADDRESS_11155111=
#Ethereum Classic (61) — blank until redeployed, see note below
VITE_CONTRACT_ADDRESS_61=
#Polygon Mainnet (137) — blank until redeployed, see note below
Expand All @@ -160,11 +160,18 @@ VITE_CONTRACT_ADDRESS_137=
VITE_WALLETCONNECT_PROJECT_ID=Your Project ID can be obtained from https://dashboard.reown.com/
```

> ⚠️ **Redeployment required.** Renaming the key registry functions changed
> their selectors, so no previously deployed Chainvoice matches the current ABI.
> Deploy `contracts/src/Chainvoice.sol` and fill in the addresses above, then
> record it in [Deployments.md](./Deployments.md). Registered keys do not carry
> over — every user must register again.

> ⚠️ Ethereum Classic and Polygon are left blank on purpose. Both still run the
> v1 contract, which stores invoice payloads on-chain as strings and has no
> public key registry, so it does not match the current ABI. The app treats any
> non-empty address as supported, so filling these in would send calls those
> contracts cannot decode. Populate them only after redeploying.

Comment thread
Atharva0506 marked this conversation as resolved.
> ⚠️ **Security Note:** Never commit `.env` files to version control. Keep your private keys secure.

### Relay configuration
Expand All @@ -186,9 +193,8 @@ VITE_RELAY_TIMEOUT_MS=

### Current (hash-based invoice storage)
Stores only `keccak256` of the invoice data on-chain and exposes the public key
registry. This is the deployment the frontend is configured against.
- Ethereum Sepolia (11155111)
```0x7bC4C5abb5b1B8355Aa65307C1cFDbe6254505d2```
registry. Awaiting redeployment after the key registry rename — see the note
under Environment Variables.

### v1 (Mainnet Deployment — Jan 1)
Stores the invoice payload on-chain as strings. Superseded, kept for reference.
Expand Down
29 changes: 16 additions & 13 deletions contracts/src/Chainvoice.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ contract Chainvoice {
error TreasuryNotSet();
error NoFeesAvailable();
error WithdrawFailed();
error InvalidWakuKey();
error InvalidPublicKey();
error InvalidInvoiceHash();

// ========== Storage ==========
Expand All @@ -61,8 +61,8 @@ contract Chainvoice {
mapping(address => uint256[]) public sentInvoices;
mapping(address => uint256[]) public receivedInvoices;

// ========== Waku Public Key Registry ==========
mapping(address => bytes) private wakuPublicKeys;
// ========== Messaging Public Key Registry ==========
mapping(address => bytes) private messagingPublicKeys;

address public owner;
address public treasuryAddress;
Expand All @@ -76,7 +76,7 @@ contract Chainvoice {
event InvoiceCancelled(uint256 indexed id, address indexed from, address indexed to, address tokenAddress);
event InvoiceBatchCreated(address indexed creator, address indexed token, uint256 count, uint256[] ids);
event InvoiceBatchPaid(address indexed payer, address indexed token, uint256 count, uint256 totalAmount, uint256[] ids);
event WakuKeyRegistered(address indexed user, bytes publicKey);
event PublicKeyRegistered(address indexed user, bytes publicKey);

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferInitiated(address indexed currentOwner, address indexed pendingOwner);
Expand Down Expand Up @@ -118,20 +118,23 @@ contract Chainvoice {
return success;
}

// ========== Waku Key Management ==========
/// @notice Register or update the caller's Waku ECIES public key.
// ========== Messaging Key Management ==========
/// @notice Register or update the caller's ECIES public key.
/// @dev Used by clients to encrypt invoice payloads for this address. The
/// registry is transport-agnostic: it says nothing about how the
/// encrypted payload is delivered.
/// @param publicKey The uncompressed secp256k1 public key (65 bytes).
function registerWakuPublicKey(bytes calldata publicKey) external {
if (publicKey.length != 65 || publicKey[0] != 0x04) revert InvalidWakuKey();
wakuPublicKeys[msg.sender] = publicKey;
emit WakuKeyRegistered(msg.sender, publicKey);
function registerPublicKey(bytes calldata publicKey) external {
if (publicKey.length != 65 || publicKey[0] != 0x04) revert InvalidPublicKey();
messagingPublicKeys[msg.sender] = publicKey;
emit PublicKeyRegistered(msg.sender, publicKey);
}

/// @notice Get a user's registered Waku public key.
/// @notice Get a user's registered ECIES public key.
/// @param user The address to look up.
/// @return The public key bytes (empty if not registered).
function getWakuPublicKey(address user) external view returns (bytes memory) {
return wakuPublicKeys[user];
function getPublicKey(address user) external view returns (bytes memory) {
return messagingPublicKeys[user];
}

// ========== Single-invoice create ==========
Expand Down
50 changes: 25 additions & 25 deletions contracts/test/Chainvoice.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ contract ChainvoiceTest is Test {
address bob = address(0xB0B);
address charlie = address(0xC4A7);

event WakuKeyRegistered(address indexed user, bytes publicKey);
event PublicKeyRegistered(address indexed user, bytes publicKey);

function setUp() public {
chainvoice = new Chainvoice();
Expand Down Expand Up @@ -311,10 +311,10 @@ contract ChainvoiceTest is Test {
}

/* ------------------------------------------------------------ */
/* WAKU KEY REGISTRY */
/* MESSAGING KEY REGISTRY */
/* ------------------------------------------------------------ */

function testRegisterWakuPublicKey() public {
function testRegisterPublicKey() public {
// 65-byte uncompressed secp256k1 public key (0x04 prefix + 64 bytes)
bytes memory pubKey = new bytes(65);
pubKey[0] = 0x04;
Expand All @@ -323,29 +323,29 @@ contract ChainvoiceTest is Test {
}

vm.prank(alice);
chainvoice.registerWakuPublicKey(pubKey);
chainvoice.registerPublicKey(pubKey);

bytes memory stored = chainvoice.getWakuPublicKey(alice);
bytes memory stored = chainvoice.getPublicKey(alice);
assertEq(stored.length, 65);
assertEq(stored[0], pubKey[0]);
assertEq(stored[64], pubKey[64]);
}

function testRegisterWakuPublicKey_EmitsEvent() public {
function testRegisterPublicKey_EmitsEvent() public {
bytes memory pubKey = new bytes(65);
pubKey[0] = 0x04;
for (uint256 i = 1; i < 65; i++) {
pubKey[i] = bytes1(uint8(i + 100));
}

vm.expectEmit(true, false, false, true);
emit WakuKeyRegistered(alice, pubKey);
emit PublicKeyRegistered(alice, pubKey);

vm.prank(alice);
chainvoice.registerWakuPublicKey(pubKey);
chainvoice.registerPublicKey(pubKey);
}

function testUpdateWakuPublicKey() public {
function testUpdatePublicKey() public {
bytes memory key1 = new bytes(65);
key1[0] = 0x04;
for (uint256 i = 1; i < 65; i++) key1[i] = bytes1(uint8(i));
Expand All @@ -355,21 +355,21 @@ contract ChainvoiceTest is Test {
for (uint256 i = 1; i < 65; i++) key2[i] = bytes1(uint8(i + 50));

vm.startPrank(alice);
chainvoice.registerWakuPublicKey(key1);
chainvoice.registerPublicKey(key1);

bytes memory stored1 = chainvoice.getWakuPublicKey(alice);
bytes memory stored1 = chainvoice.getPublicKey(alice);
assertEq(keccak256(stored1), keccak256(key1));

// Update to a new key
chainvoice.registerWakuPublicKey(key2);
chainvoice.registerPublicKey(key2);
vm.stopPrank();

bytes memory stored2 = chainvoice.getWakuPublicKey(alice);
bytes memory stored2 = chainvoice.getPublicKey(alice);
assertEq(keccak256(stored2), keccak256(key2));
}

function testGetWakuPublicKey_Unregistered() public {
bytes memory stored = chainvoice.getWakuPublicKey(address(0xDEAD));
function testGetPublicKey_Unregistered() public {
bytes memory stored = chainvoice.getPublicKey(address(0xDEAD));
assertEq(stored.length, 0);
}

Expand All @@ -383,21 +383,21 @@ contract ChainvoiceTest is Test {
for (uint256 i = 1; i < 65; i++) bobKey[i] = bytes1(uint8(i + 50));

vm.prank(alice);
chainvoice.registerWakuPublicKey(aliceKey);
chainvoice.registerPublicKey(aliceKey);

vm.prank(bob);
chainvoice.registerWakuPublicKey(bobKey);
chainvoice.registerPublicKey(bobKey);

assertEq(keccak256(chainvoice.getWakuPublicKey(alice)), keccak256(aliceKey));
assertEq(keccak256(chainvoice.getWakuPublicKey(bob)), keccak256(bobKey));
assertEq(keccak256(chainvoice.getPublicKey(alice)), keccak256(aliceKey));
assertEq(keccak256(chainvoice.getPublicKey(bob)), keccak256(bobKey));
}

function testRegisterWakuPublicKey_RevertIfInvalidLength() public {
function testRegisterPublicKey_RevertIfInvalidLength() public {
bytes memory shortKey = hex"04aabbccdd";

vm.prank(alice);
vm.expectRevert(Chainvoice.InvalidWakuKey.selector);
chainvoice.registerWakuPublicKey(shortKey);
vm.expectRevert(Chainvoice.InvalidPublicKey.selector);
chainvoice.registerPublicKey(shortKey);
}

function testCreateInvoice_RevertIfZeroHash() public {
Expand All @@ -406,14 +406,14 @@ contract ChainvoiceTest is Test {
chainvoice.createInvoice(bob, 1 ether, address(0), bytes32(0));
}

function testRegisterWakuPublicKey_RevertIfInvalidPrefix() public {
function testRegisterPublicKey_RevertIfInvalidPrefix() public {
bytes memory badPrefixKey = new bytes(65);
badPrefixKey[0] = 0x03; // wrong prefix, should be 0x04
for (uint256 i = 1; i < 65; i++) badPrefixKey[i] = bytes1(uint8(i));

vm.prank(alice);
vm.expectRevert(Chainvoice.InvalidWakuKey.selector);
chainvoice.registerWakuPublicKey(badPrefixKey);
vm.expectRevert(Chainvoice.InvalidPublicKey.selector);
chainvoice.registerPublicKey(badPrefixKey);
}

function testCreateInvoiceWithDataHash() public {
Expand Down
7 changes: 6 additions & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# env-copy

# REDEPLOYMENT REQUIRED. Renaming the registry functions changed their
# selectors, so no currently deployed Chainvoice matches this ABI. Deploy the
# current contracts/src/Chainvoice.sol and put the new addresses here.
# Registered keys do not carry over — every user must register again.

#Ethereum Sepolia (11155111)
VITE_CONTRACT_ADDRESS_11155111=0x7bC4C5abb5b1B8355Aa65307C1cFDbe6254505d2
VITE_CONTRACT_ADDRESS_11155111=

# Left blank deliberately. Both chains still run the v1 contract, which stores
# invoice payloads on-chain as strings and has no public key registry, so it
Expand Down
9 changes: 7 additions & 2 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,20 @@ http://localhost:5173

The frontend reads Vite environment variables from `.env`.

The provided `.env.example` includes deployed contract addresses for supported networks and the WalletConnect project ID placeholder:
The provided `.env.example` lists the contract addresses per network alongside the WalletConnect project ID placeholder:

```env
VITE_CONTRACT_ADDRESS_11155111=0x7bC4C5abb5b1B8355Aa65307C1cFDbe6254505d2
VITE_CONTRACT_ADDRESS_11155111=
VITE_CONTRACT_ADDRESS_61=
VITE_CONTRACT_ADDRESS_137=
VITE_WALLETCONNECT_PROJECT_ID=
```

> ⚠️ **Redeployment required.** Renaming the key registry functions changed
> their selectors, so no previously deployed Chainvoice matches the current ABI.
> Deploy `contracts/src/Chainvoice.sol` and fill in the addresses above.
> Registered keys do not carry over — every user must register again.

> ⚠️ Ethereum Classic and Polygon are left blank on purpose. Both still run the
> v1 contract, which does not match the current ABI. The app treats any
> non-empty address as supported, so filling these in would send calls those
Expand Down
Loading
Loading