crypto-stable-pay 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/dist/erc20.d.ts +6 -0
- package/dist/erc20.js +18 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/networks.d.ts +2 -0
- package/dist/networks.js +10 -0
- package/dist/pay.d.ts +17 -0
- package/dist/pay.js +93 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# crypto-stable-pay
|
|
2
|
+
|
|
3
|
+
Accept USDC/USDT (or any ERC-20) on multiple EVM networks using user wallets (MetaMask, Trust Wallet browser, Binance Web3 wallet, etc.).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
npm i crypto-stable-pay ethers
|
|
7
|
+
|
|
8
|
+
## Basic usage (transfer)
|
|
9
|
+
```ts
|
|
10
|
+
import { connectWallet, payWithErc20, waitForReceipt } from "crypto-stable-pay";
|
|
11
|
+
|
|
12
|
+
const eip1193 = window.ethereum; // injected by wallet
|
|
13
|
+
|
|
14
|
+
const from = await connectWallet(eip1193);
|
|
15
|
+
|
|
16
|
+
const result = await payWithErc20(eip1193, {
|
|
17
|
+
chainId: 137, // Polygon
|
|
18
|
+
token: {
|
|
19
|
+
symbol: "USDC",
|
|
20
|
+
address: "0x...", // USDC contract on Polygon (you provide)
|
|
21
|
+
decimals: 6
|
|
22
|
+
},
|
|
23
|
+
recipient: "0xYourMerchantWallet",
|
|
24
|
+
amount: "12.50",
|
|
25
|
+
reference: "order_123"
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const receipt = await waitForReceipt(eip1193, result.txHash, 1);
|
|
29
|
+
console.log({ result, receipt });
|
package/dist/erc20.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Contract, BrowserProvider } from "ethers";
|
|
2
|
+
import type { Address, Eip1193Provider } from "./types";
|
|
3
|
+
export declare const ERC20_ABI: readonly ["function decimals() view returns (uint8)", "function symbol() view returns (string)", "function allowance(address owner, address spender) view returns (uint256)", "function approve(address spender, uint256 value) returns (bool)", "function transfer(address to, uint256 value) returns (bool)", "function transferFrom(address from, address to, uint256 value) returns (bool)"];
|
|
4
|
+
export declare function getBrowserProvider(eip1193: Eip1193Provider): BrowserProvider;
|
|
5
|
+
export declare function getErc20(tokenAddress: Address, provider: BrowserProvider): Contract;
|
|
6
|
+
export declare function toAtomic(amountHuman: string, decimals: number): Promise<bigint>;
|
package/dist/erc20.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Contract, BrowserProvider, ethers } from "ethers";
|
|
2
|
+
export const ERC20_ABI = [
|
|
3
|
+
"function decimals() view returns (uint8)",
|
|
4
|
+
"function symbol() view returns (string)",
|
|
5
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
6
|
+
"function approve(address spender, uint256 value) returns (bool)",
|
|
7
|
+
"function transfer(address to, uint256 value) returns (bool)",
|
|
8
|
+
"function transferFrom(address from, address to, uint256 value) returns (bool)"
|
|
9
|
+
];
|
|
10
|
+
export function getBrowserProvider(eip1193) {
|
|
11
|
+
return new BrowserProvider(eip1193);
|
|
12
|
+
}
|
|
13
|
+
export function getErc20(tokenAddress, provider) {
|
|
14
|
+
return new Contract(tokenAddress, ERC20_ABI, provider);
|
|
15
|
+
}
|
|
16
|
+
export async function toAtomic(amountHuman, decimals) {
|
|
17
|
+
return ethers.parseUnits(amountHuman, decimals);
|
|
18
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/networks.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// You can ship a few common ones, but do NOT hardcode token addresses here.
|
|
2
|
+
// Site owners pass USDC/USDT contract addresses per chain.
|
|
3
|
+
export const NETWORKS = {
|
|
4
|
+
1: { chainId: 1, name: "Ethereum Mainnet" },
|
|
5
|
+
56: { chainId: 56, name: "BNB Smart Chain" },
|
|
6
|
+
137: { chainId: 137, name: "Polygon" },
|
|
7
|
+
42161: { chainId: 42161, name: "Arbitrum One" },
|
|
8
|
+
10: { chainId: 10, name: "Optimism" },
|
|
9
|
+
8453: { chainId: 8453, name: "Base" }
|
|
10
|
+
};
|
package/dist/pay.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Address, Eip1193Provider, PaymentReceipt, PaymentRequest, PaymentResult } from "./types";
|
|
2
|
+
export declare function connectWallet(eip1193: Eip1193Provider): Promise<Address>;
|
|
3
|
+
export declare function getChainId(eip1193: Eip1193Provider): Promise<number>;
|
|
4
|
+
export declare function switchChain(eip1193: Eip1193Provider, chainId: number): Promise<void>;
|
|
5
|
+
export declare function addChain(eip1193: Eip1193Provider, chain: {
|
|
6
|
+
chainId: number;
|
|
7
|
+
chainName: string;
|
|
8
|
+
rpcUrls: string[];
|
|
9
|
+
nativeCurrency: {
|
|
10
|
+
name: string;
|
|
11
|
+
symbol: string;
|
|
12
|
+
decimals: number;
|
|
13
|
+
};
|
|
14
|
+
blockExplorerUrls?: string[];
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
export declare function payWithErc20(eip1193: Eip1193Provider, req: PaymentRequest): Promise<PaymentResult>;
|
|
17
|
+
export declare function waitForReceipt(eip1193: Eip1193Provider, txHash: string, confirmations?: number): Promise<PaymentReceipt>;
|
package/dist/pay.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { getBrowserProvider, getErc20, toAtomic } from "./erc20";
|
|
2
|
+
function asAddress(v) {
|
|
3
|
+
if (typeof v !== "string" || !v.startsWith("0x"))
|
|
4
|
+
throw new Error("Invalid address");
|
|
5
|
+
return v;
|
|
6
|
+
}
|
|
7
|
+
export async function connectWallet(eip1193) {
|
|
8
|
+
const accounts = (await eip1193.request({ method: "eth_requestAccounts" }));
|
|
9
|
+
if (!accounts?.length)
|
|
10
|
+
throw new Error("No accounts returned");
|
|
11
|
+
return asAddress(accounts[0]);
|
|
12
|
+
}
|
|
13
|
+
export async function getChainId(eip1193) {
|
|
14
|
+
const hex = (await eip1193.request({ method: "eth_chainId" }));
|
|
15
|
+
return Number.parseInt(hex, 16);
|
|
16
|
+
}
|
|
17
|
+
export async function switchChain(eip1193, chainId) {
|
|
18
|
+
const hexChainId = "0x" + chainId.toString(16);
|
|
19
|
+
await eip1193.request({
|
|
20
|
+
method: "wallet_switchEthereumChain",
|
|
21
|
+
params: [{ chainId: hexChainId }]
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// Optional: only needed if you want to support adding unknown chains automatically.
|
|
25
|
+
export async function addChain(eip1193, chain) {
|
|
26
|
+
const hexChainId = "0x" + chain.chainId.toString(16);
|
|
27
|
+
await eip1193.request({
|
|
28
|
+
method: "wallet_addEthereumChain",
|
|
29
|
+
params: [{
|
|
30
|
+
chainId: hexChainId,
|
|
31
|
+
chainName: chain.chainName,
|
|
32
|
+
rpcUrls: chain.rpcUrls,
|
|
33
|
+
nativeCurrency: chain.nativeCurrency,
|
|
34
|
+
blockExplorerUrls: chain.blockExplorerUrls ?? []
|
|
35
|
+
}]
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export async function payWithErc20(eip1193, req) {
|
|
39
|
+
const provider = getBrowserProvider(eip1193);
|
|
40
|
+
const signer = await provider.getSigner();
|
|
41
|
+
const from = asAddress(await signer.getAddress());
|
|
42
|
+
const currentChain = await getChainId(eip1193);
|
|
43
|
+
if (currentChain !== req.chainId) {
|
|
44
|
+
// you can choose to auto-switch or throw
|
|
45
|
+
await switchChain(eip1193, req.chainId);
|
|
46
|
+
}
|
|
47
|
+
const amountAtomic = await toAtomic(req.amount, req.token.decimals);
|
|
48
|
+
const base = getErc20(req.token.address, provider).connect(signer);
|
|
49
|
+
const erc20 = base;
|
|
50
|
+
if (req.requireApproval) {
|
|
51
|
+
if (!req.spender)
|
|
52
|
+
throw new Error("spender is required when requireApproval=true");
|
|
53
|
+
const allowance = await erc20.allowance(from, req.spender);
|
|
54
|
+
if (allowance < amountAtomic) {
|
|
55
|
+
const approveTx = await erc20.approve(req.spender, amountAtomic);
|
|
56
|
+
await approveTx.wait(1);
|
|
57
|
+
}
|
|
58
|
+
const tx = await erc20.transferFrom(from, req.recipient, amountAtomic);
|
|
59
|
+
return {
|
|
60
|
+
txHash: tx.hash,
|
|
61
|
+
chainId: req.chainId,
|
|
62
|
+
from,
|
|
63
|
+
toToken: req.token.address,
|
|
64
|
+
recipient: req.recipient,
|
|
65
|
+
amountAtomic,
|
|
66
|
+
reference: req.reference
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const tx = await erc20.transfer(req.recipient, amountAtomic);
|
|
71
|
+
return {
|
|
72
|
+
txHash: tx.hash,
|
|
73
|
+
chainId: req.chainId,
|
|
74
|
+
from,
|
|
75
|
+
toToken: req.token.address,
|
|
76
|
+
recipient: req.recipient,
|
|
77
|
+
amountAtomic,
|
|
78
|
+
reference: req.reference
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export async function waitForReceipt(eip1193, txHash, confirmations = 1) {
|
|
83
|
+
const provider = getBrowserProvider(eip1193);
|
|
84
|
+
const receipt = await provider.waitForTransaction(txHash, confirmations);
|
|
85
|
+
if (!receipt)
|
|
86
|
+
throw new Error("No receipt");
|
|
87
|
+
return {
|
|
88
|
+
status: receipt.status === 1 ? "confirmed" : "failed",
|
|
89
|
+
confirmations,
|
|
90
|
+
blockNumber: receipt.blockNumber,
|
|
91
|
+
txHash
|
|
92
|
+
};
|
|
93
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type Address = `0x${string}`;
|
|
2
|
+
export type Eip1193Provider = {
|
|
3
|
+
request(args: {
|
|
4
|
+
method: string;
|
|
5
|
+
params?: unknown[] | object;
|
|
6
|
+
}): Promise<unknown>;
|
|
7
|
+
};
|
|
8
|
+
export type TokenSpec = {
|
|
9
|
+
symbol: "USDC" | "USDT" | string;
|
|
10
|
+
address: Address;
|
|
11
|
+
decimals: number;
|
|
12
|
+
};
|
|
13
|
+
export type NetworkSpec = {
|
|
14
|
+
chainId: number;
|
|
15
|
+
name: string;
|
|
16
|
+
rpcUrls?: string[];
|
|
17
|
+
nativeCurrency?: {
|
|
18
|
+
name: string;
|
|
19
|
+
symbol: string;
|
|
20
|
+
decimals: number;
|
|
21
|
+
};
|
|
22
|
+
blockExplorerUrls?: string[];
|
|
23
|
+
};
|
|
24
|
+
export type PaymentRequest = {
|
|
25
|
+
chainId: number;
|
|
26
|
+
token: TokenSpec;
|
|
27
|
+
recipient: Address;
|
|
28
|
+
amount: string;
|
|
29
|
+
reference?: string;
|
|
30
|
+
requireApproval?: boolean;
|
|
31
|
+
spender?: Address;
|
|
32
|
+
};
|
|
33
|
+
export type PaymentResult = {
|
|
34
|
+
txHash: string;
|
|
35
|
+
chainId: number;
|
|
36
|
+
from: Address;
|
|
37
|
+
toToken: Address;
|
|
38
|
+
recipient: Address;
|
|
39
|
+
amountAtomic: bigint;
|
|
40
|
+
reference?: string;
|
|
41
|
+
};
|
|
42
|
+
export type PaymentReceipt = {
|
|
43
|
+
status: "confirmed" | "failed";
|
|
44
|
+
confirmations: number;
|
|
45
|
+
blockNumber: number;
|
|
46
|
+
txHash: string;
|
|
47
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "crypto-stable-pay",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Accept USDC/USDT payments on multiple EVM networks via user wallets (MetaMask, Trust, Binance, ...)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"dev": "tsc -w -p tsconfig.json",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"usdc",
|
|
21
|
+
"usdt",
|
|
22
|
+
"payments",
|
|
23
|
+
"web3",
|
|
24
|
+
"erc20",
|
|
25
|
+
"metamask",
|
|
26
|
+
"evm"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"ethers": "^6.16.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "^5.5.4"
|
|
34
|
+
}
|
|
35
|
+
}
|