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
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ jobs:
VITE_CONTRACT_ADDRESS_11155111: ${{ secrets.VITE_CONTRACT_ADDRESS_11155111 }}
VITE_CONTRACT_ADDRESS_63: ${{ secrets.VITE_CONTRACT_ADDRESS_63 }}
VITE_CONTRACT_ADDRESS_80002: ${{ secrets.VITE_CONTRACT_ADDRESS_80002 }}
VITE_CONTRACT_ADDRESS_8453: ${{ secrets.VITE_CONTRACT_ADDRESS_8453 }}
# Ethereum Classic and Polygon are deliberately not injected. Both
# still run the v1 contract, which stores payloads on-chain as strings
# and has no key registry, so it does not match this ABI. The app
Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,13 @@ npm run dev
### Frontend Configuration (`frontend/.env`)
```.env
#Ethereum Sepolia (11155111)
VITE_CONTRACT_ADDRESS_11155111=0x65eb0ca96f972c5a0cdaa623a5b54650e499df5b
VITE_CONTRACT_ADDRESS_11155111=0xff10Cda545E10656C2DcBEAB6C9eC375E136363a
#Mordor Testnet (63)
VITE_CONTRACT_ADDRESS_63=0xeF6669DDEd0078A8Cfdd758A40D194b1cC2120d2
#Polygon Amoy (80002)
VITE_CONTRACT_ADDRESS_80002=0xeF6669DDEd0078A8Cfdd758A40D194b1cC2120d2
#Base (8453) — blank until deployed
VITE_CONTRACT_ADDRESS_8453=
#Ethereum Classic (61) — blank until redeployed, see note below
VITE_CONTRACT_ADDRESS_61=
#Polygon Mainnet (137) — blank until redeployed, see note below
Expand All @@ -206,8 +212,8 @@ VITE_CONTRACT_ADDRESS_137=

> ⚠️ Renaming the key registry functions changed their selectors, so only a
> contract deployed from the current `contracts/src/Chainvoice.sol` will answer.
> The Sepolia address above is such a deployment — see
> [Deployments.md](./Deployments.md). Keys registered against an earlier
> The addresses above are such deployments, and
> [Deployments.md](./Deployments.md) is the record of every deployment. Keys registered against an earlier
> deployment do not carry over; those users must register again.
>
> ⚠️ Ethereum Classic and Polygon are left blank on purpose. Both still run the
Expand Down Expand Up @@ -237,9 +243,17 @@ VITE_RELAY_TIMEOUT_MS=

### Current (hash-based invoice storage)
Stores only `keccak256` of the invoice data on-chain and exposes the public key
registry. Addresses are tracked in [Deployments.md](./Deployments.md).
registry. [Deployments.md](./Deployments.md) is the full record, including
constructor parameters and retired addresses.
- Ethereum Sepolia (11155111)
```0x65eb0ca96f972c5a0cdaa623a5b54650e499df5b```
```0xff10Cda545E10656C2DcBEAB6C9eC375E136363a```
- Mordor Testnet (63)
```0xeF6669DDEd0078A8Cfdd758A40D194b1cC2120d2```
- Polygon Amoy (80002)
```0xeF6669DDEd0078A8Cfdd758A40D194b1cC2120d2```

Retired, kept for reference: Ethereum Sepolia ```0x65eb0ca96f972c5a0cdaa623a5b54650e499df5b```
(older ABI with `owner` and `fee`, which the current frontend cannot use).

### v1 (Mainnet Deployment — Jan 1)
Stores the invoice payload on-chain as strings. Superseded, kept for reference.
Expand Down
16 changes: 10 additions & 6 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# env-copy

# Renaming the registry functions changed their selectors, so only a contract
# deployed from the current contracts/src/Chainvoice.sol will answer. Sepolia
# below is such a deployment; see Deployments.md. Keys registered against any
# earlier deployment do not carry over — those users must register again.
# deployed from the current contracts/src/Chainvoice.sol will answer. The
# addresses below are such deployments; Deployments.md records every one. Keys
# registered against any earlier deployment do not carry over — those users
# must register again.

#Ethereum Sepolia (11155111)
VITE_CONTRACT_ADDRESS_11155111=0x65eb0ca96f972c5a0cdaa623a5b54650e499df5b
VITE_CONTRACT_ADDRESS_11155111=0xff10Cda545E10656C2DcBEAB6C9eC375E136363a

# 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 All @@ -17,10 +18,13 @@ VITE_CONTRACT_ADDRESS_11155111=0x65eb0ca96f972c5a0cdaa623a5b54650e499df5b
VITE_CONTRACT_ADDRESS_61=

# Mordor Testnet (63)
VITE_CONTRACT_ADDRESS_63=
VITE_CONTRACT_ADDRESS_63=0xeF6669DDEd0078A8Cfdd758A40D194b1cC2120d2

# Polygon Amoy (80002)
VITE_CONTRACT_ADDRESS_80002=
VITE_CONTRACT_ADDRESS_80002=0xeF6669DDEd0078A8Cfdd758A40D194b1cC2120d2

# Base (8453), blank until deployed
VITE_CONTRACT_ADDRESS_8453=

# Polygon Mainnet (137)
VITE_CONTRACT_ADDRESS_137=
Expand Down
44 changes: 44 additions & 0 deletions frontend/src/components/CustomTokenDetails.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { AlertTriangle } from "lucide-react";
import { useTranslation } from "@/hooks/useTranslation";
import { Badge } from "@/components/ui/badge";
import { CopyButton } from "@/components/ui/copyButton";

/**
* A custom token read from its contract. Reading it proves only that the
* address is an ERC-20: anyone can deploy one called "USD Coin", so the
* address, not the name, is what the user has to check.
*/
export default function CustomTokenDetails({ token }) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
const { t } = useTranslation("customToken");

return (
<div className="bg-amber-50 p-4 rounded-lg border border-amber-200">
<div className="flex items-start gap-3">
<AlertTriangle
className="h-5 w-5 text-amber-600 flex-shrink-0 mt-0.5"
aria-hidden="true"
/>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-2 mb-2">
<p className="font-medium text-amber-900">
{token.name} ({token.symbol})
</p>
<Badge className="bg-amber-100 text-amber-800 text-xs">
{t("unlisted")}
</Badge>
</div>
<div className="flex items-center gap-2 mb-2">
<span className="text-sm text-amber-800 font-mono break-all">
{token.address}
</span>
<CopyButton textToCopy={token.address} />
</div>
<p className="text-xs text-amber-800">
{t("decimals", { decimals: String(token.decimals) })}
</p>
<p className="text-xs text-amber-800 mt-1">{t("checkAddress")}</p>
</div>
</div>
</div>
);
}
4 changes: 3 additions & 1 deletion frontend/src/components/ui/input.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from "react"

import { cn } from "@/lib/utils"
import { getInputWheelHandler } from "@/utils/numberInputWheel"

const Input = React.forwardRef(({ className, type, ...props }, ref) => {
const Input = React.forwardRef(({ className, type, onWheel, ...props }, ref) => {
return (
(<input
type={type}
Expand All @@ -11,6 +12,7 @@ const Input = React.forwardRef(({ className, type, ...props }, ref) => {
className
)}
ref={ref}
onWheel={getInputWheelHandler(type, onWheel)}
{...props} />)
);
})
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,13 @@
"walletCancelled": "You declined the cancellation in your wallet.",
"generic": "Failed to cancel the invoice. Please try again."
}
},
"customToken": {
"unlisted": "Not in token list",
"decimals": "Decimals: {{decimals}}",
"checkAddress": "Anyone can deploy a token with any name. Make sure this address belongs to the token you expect before using it."
},
"generateLink": {
"connectWalletPlaceholder": "Connect your wallet to generate a link"
}
}
32 changes: 2 additions & 30 deletions frontend/src/page/CreateInvoice.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
} from "@/components/ui/popover";
import { Calendar } from "@/components/ui/calendar";
import {
Badge,
CalendarIcon,
CheckCircle2,
Coins,
Expand All @@ -45,7 +44,7 @@ import { ERC20_ABI } from "@/contractsABI/ERC20_ABI";

import WalletConnectionAlert from "../components/WalletConnectionAlert";
import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker";
import { CopyButton } from "@/components/ui/copyButton";
import CustomTokenDetails from "@/components/CustomTokenDetails";
import CountryPicker from "@/components/CountryPicker";
import { useTokenList, isTestnet } from "@/hooks/useTokenList";
import {
Expand Down Expand Up @@ -1307,34 +1306,7 @@ function CreateInvoice() {
{tokenVerificationState === "success" &&
verifiedToken && (
<div className="mt-3">
<div className="bg-green-50 p-4 rounded-lg border border-green-200">
<div className="flex items-start gap-3">
<CheckCircle2 className="h-6 w-6 text-green-500 flex-shrink-0 mt-0.5" />
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<p className="font-medium text-green-800">
{verifiedToken.name} (
{verifiedToken.symbol})
</p>
<Badge className="bg-green-100 text-green-700 text-xs">
Verified ✓
</Badge>
</div>
<div className="flex items-center gap-2 mb-2">
<span className="text-sm text-green-600 font-mono">
{verifiedToken.address}
</span>
<CopyButton
textToCopy={verifiedToken.address}
/>
</div>
<p className="text-xs text-green-600">
Decimals: {String(verifiedToken.decimals)} •
Contract verified and ready to use
</p>
</div>
</div>
</div>
<CustomTokenDetails token={verifiedToken} />
<TokenIntegrationRequest
address={customTokenAddress}
/>
Expand Down
30 changes: 2 additions & 28 deletions frontend/src/page/CreateInvoicesBatch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
} from "@/components/ui/popover";
import { Calendar } from "@/components/ui/calendar";
import {
Badge,
CalendarIcon,
CheckCircle2,
Coins,
Expand Down Expand Up @@ -54,9 +53,9 @@ import TokenIntegrationRequest from "@/components/TokenIntegrationRequest";
import { ERC20_ABI } from "@/contractsABI/ERC20_ABI";
import WalletConnectionAlert from "../components/WalletConnectionAlert";
import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker";
import CustomTokenDetails from "@/components/CustomTokenDetails";
import { getMaxInvoicesPerBatch } from "@/utils/batchLimits";
import { AmountTypeToggle } from "../components/AmountTypeToggle";
import { CopyButton } from "@/components/ui/copyButton";
import CountryPicker from "@/components/CountryPicker";
import {
getLineAmountDetails,
Expand Down Expand Up @@ -1103,32 +1102,7 @@ function CreateInvoicesBatch() {

{tokenVerificationState === "success" && verifiedToken && (
<div className="mt-3">
<div className="bg-green-50 p-4 rounded-lg border border-green-200">
<div className="flex items-start gap-3">
<CheckCircle2 className="h-5 w-5 text-green-500 mt-0.5" />
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<p className="font-medium text-green-800">
{verifiedToken.name} ({verifiedToken.symbol})
</p>
<Badge className="bg-green-100 text-green-700 text-xs">
Verified
</Badge>
</div>
<div className="flex items-center gap-2 mb-1">
<span className="text-sm text-green-600 font-mono">
{verifiedToken.address}
</span>
<CopyButton
textToCopy={verifiedToken.address}
/>
</div>
<p className="text-xs text-green-600">
Decimals: {String(verifiedToken.decimals)}
</p>
</div>
</div>
</div>
<CustomTokenDetails token={verifiedToken} />
<TokenIntegrationRequest address={customTokenAddress} />
</div>
)}
Expand Down
51 changes: 12 additions & 39 deletions frontend/src/page/GenerateLink.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
Check,
Wallet,
Loader2,
CheckCircle2,
XCircle,
Coins,
} from "lucide-react";
Expand All @@ -18,18 +17,19 @@ import { Label } from "../components/ui/label";
import WalletConnectionAlert from "@/components/WalletConnectionAlert";
import TokenIntegrationRequest from "@/components/TokenIntegrationRequest";
import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker";
import { CopyButton } from "@/components/ui/copyButton";
import { Badge } from "@/components/ui/badge";
import CustomTokenDetails from "@/components/CustomTokenDetails";
import { BrowserProvider, ethers } from "ethers";
import { ERC20_ABI } from "@/contractsABI/ERC20_ABI";
import { PAGE_CONTAINER } from "@/utils/layout";
import { cn } from "@/lib/utils";
import toast from "react-hot-toast";
import { verifyTokenContract } from "../utils/paymentToken.js";
import { useTranslation } from "../hooks/useTranslation";

const GenerateLink = () => {
const { address, isConnected, chainId } = useAccount();
const { data: walletClient } = useWalletClient();
const { t } = useTranslation("generateLink");

const [copied, setCopied] = useState(false);
const [amount] = useState("");
Expand Down Expand Up @@ -69,18 +69,22 @@ const GenerateLink = () => {
// Never generate a link using an unverified custom token.
if (customTokenUnverified) return "";

// The link bills the connected wallet on its chain; without one it would
// carry an empty address and a guessed chain.
if (!isConnected || !address || !chainId) return "";

// Support deployments served from a subdirectory.
const appRoot = `${window.location.origin}${import.meta.env.BASE_URL}`;

if (!tokenToUse) {
return `${appRoot}#/dashboard/create?clientAddress=${address || ""}`;
return `${appRoot}#/dashboard/create?clientAddress=${address}`;
}

const params = new URLSearchParams({
clientAddress: address || "",
clientAddress: address,
tokenAddress: tokenToUse.address || tokenToUse.contract_address,
customToken: useCustomToken ? "true" : "false",
chain: chainId?.toString() || "1",
chain: chainId.toString(),
});

if (amount) {
Expand Down Expand Up @@ -394,39 +398,7 @@ const GenerateLink = () => {
{tokenVerificationState === "success" &&
verifiedToken && (
<div className="mt-3">
<div className="bg-green-50 p-4 rounded-lg border border-green-200">
<div className="flex items-start gap-3">
<CheckCircle2 className="h-6 w-6 text-green-500 flex-shrink-0 mt-0.5" />

<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<p className="font-medium text-green-800">
{verifiedToken.name} (
{verifiedToken.symbol})
</p>

<Badge className="bg-green-100 text-green-700 text-xs">
Verified ✓
</Badge>
</div>

<div className="flex items-center gap-2 mb-2">
<span className="text-sm text-green-600 font-mono">
{verifiedToken.address}
</span>

<CopyButton
textToCopy={verifiedToken.address}
/>
</div>

<p className="text-xs text-green-600">
Decimals: {String(verifiedToken.decimals)}{" "}
• Contract verified and ready to use
</p>
</div>
</div>
</div>
<CustomTokenDetails token={verifiedToken} />

<TokenIntegrationRequest
address={customTokenAddress}
Expand Down Expand Up @@ -522,6 +494,7 @@ const GenerateLink = () => {
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 mb-3">
<Input
value={generateLink()}
placeholder={t("connectWalletPlaceholder")}
readOnly
className="bg-gray-50 text-gray-600 border-gray-300 text-xs font-mono flex-1"
/>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/page/ReceivedInvoice.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1788,9 +1788,9 @@ function ReceivedInvoice() {
<div className="mt-1">
<Chip
icon={<LayersIcon />}
label={`Batch #${invoice.batchInfo.batchId.slice(
-4
)} (${invoice.batchInfo.index + 1}/${invoice.batchInfo.batchSize
label={`Batch #${String(
invoice.batchInfo.batchId
).slice(-4)} (${invoice.batchInfo.index + 1}/${invoice.batchInfo.batchSize
})`}
size="small"
variant="outlined"
Expand Down
Loading
Loading