@bvdaniel/confidential-pay-core 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A0x
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,65 @@
1
+ export declare const USDC_ADDRESS: Record<string, `0x${string}`>;
2
+ export declare const CHAIN: Record<string, any>;
3
+ export interface ConfidentialPayOptions {
4
+ apiKeyId?: string;
5
+ apiKeySecret?: string;
6
+ walletSecret?: string;
7
+ network?: string;
8
+ }
9
+ export declare class ConfidentialPay {
10
+ private client;
11
+ private network;
12
+ private publicClient;
13
+ constructor(options?: ConfidentialPayOptions);
14
+ get networkName(): string;
15
+ /**
16
+ * Creates a new server-controlled EOA on Base.
17
+ */
18
+ createWallet(name?: string): Promise<{
19
+ address: `0x${string}`;
20
+ }>;
21
+ /**
22
+ * Get or create a named wallet. Idempotent.
23
+ */
24
+ getOrCreateWallet(name: string): Promise<{
25
+ address: `0x${string}`;
26
+ }>;
27
+ /**
28
+ * Requests testnet ETH from the CDP faucet (only works on testnets).
29
+ */
30
+ faucetEth(address: `0x${string}`): Promise<`0x${string}`>;
31
+ /**
32
+ * Requests testnet USDC from the CDP faucet (only works on testnets).
33
+ */
34
+ faucetUsdc(address: `0x${string}`): Promise<`0x${string}`>;
35
+ /**
36
+ * Waits for a transaction to reach finality on the configured network.
37
+ */
38
+ waitForReceipt(txHash: `0x${string}`): Promise<any>;
39
+ /**
40
+ * Gets the USDC balance for a wallet address.
41
+ */
42
+ getUsdcBalance(address: `0x${string}`): Promise<string>;
43
+ /**
44
+ * Gets the native ETH balance for a wallet address (for gas).
45
+ */
46
+ getNativeBalance(address: `0x${string}`): Promise<string>;
47
+ /**
48
+ * Sends a USDC payment from a wallet address to a recipient.
49
+ * Returns the transaction hash.
50
+ */
51
+ sendUsdcPayment(params: {
52
+ from: `0x${string}`;
53
+ to: `0x${string}`;
54
+ amount: string;
55
+ }): Promise<{
56
+ transactionHash: `0x${string}`;
57
+ network: string;
58
+ }>;
59
+ /**
60
+ * Funds a wallet for the first time on testnets: USDC if below 1, ETH gas if below 0.001.
61
+ * Skips both if already funded. Fails silently on faucet rate-limits.
62
+ * @returns true if the wallet holds >= 1 USDC after this call.
63
+ */
64
+ fundOnFirstSend(address: `0x${string}`): Promise<boolean>;
65
+ }
package/dist/index.js ADDED
@@ -0,0 +1,194 @@
1
+ import { CdpClient } from "@coinbase/cdp-sdk";
2
+ import { createPublicClient, http, encodeFunctionData, parseUnits, formatUnits } from "viem";
3
+ import { base, baseSepolia } from "viem/chains";
4
+ export const USDC_ADDRESS = {
5
+ "base-mainnet": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
6
+ "base-sepolia": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
7
+ };
8
+ export const CHAIN = {
9
+ "base-mainnet": base,
10
+ "base-sepolia": baseSepolia,
11
+ };
12
+ const ERC20_TRANSFER_ABI = [
13
+ {
14
+ type: "function",
15
+ name: "transfer",
16
+ stateMutability: "nonpayable",
17
+ inputs: [
18
+ { name: "to", type: "address" },
19
+ { name: "value", type: "uint256" },
20
+ ],
21
+ outputs: [{ name: "", type: "bool" }],
22
+ },
23
+ ];
24
+ const ERC20_BALANCE_ABI = [
25
+ {
26
+ type: "function",
27
+ name: "balanceOf",
28
+ stateMutability: "view",
29
+ inputs: [{ name: "account", type: "address" }],
30
+ outputs: [{ name: "", type: "uint256" }],
31
+ },
32
+ ];
33
+ function applyTimeout(ms) {
34
+ return new Promise((resolve) => setTimeout(resolve, ms));
35
+ }
36
+ export class ConfidentialPay {
37
+ client;
38
+ network;
39
+ publicClient;
40
+ constructor(options = {}) {
41
+ this.client = new CdpClient({
42
+ apiKeyId: options.apiKeyId || process.env.CDP_API_KEY_ID,
43
+ apiKeySecret: options.apiKeySecret || process.env.CDP_API_KEY_SECRET,
44
+ walletSecret: options.walletSecret || process.env.CDP_WALLET_SECRET,
45
+ });
46
+ this.network = options.network || process.env.CDP_NETWORK || "base-sepolia";
47
+ const chain = CHAIN[this.network];
48
+ if (!chain)
49
+ throw new Error(`Unsupported network: ${this.network}`);
50
+ this.publicClient = createPublicClient({
51
+ chain,
52
+ transport: http(),
53
+ });
54
+ }
55
+ get networkName() {
56
+ return this.network;
57
+ }
58
+ /**
59
+ * Creates a new server-controlled EOA on Base.
60
+ */
61
+ async createWallet(name) {
62
+ const account = await this.client.evm.createAccount({ name });
63
+ return { address: account.address };
64
+ }
65
+ /**
66
+ * Get or create a named wallet. Idempotent.
67
+ */
68
+ async getOrCreateWallet(name) {
69
+ const account = await this.client.evm.getOrCreateAccount({ name });
70
+ return { address: account.address };
71
+ }
72
+ /**
73
+ * Requests testnet ETH from the CDP faucet (only works on testnets).
74
+ */
75
+ async faucetEth(address) {
76
+ if (this.network === "base-mainnet") {
77
+ throw new Error("Faucet is only available on testnets.");
78
+ }
79
+ const res = await this.client.evm.requestFaucet({
80
+ address,
81
+ network: "base-sepolia",
82
+ token: "eth",
83
+ });
84
+ return res.transactionHash;
85
+ }
86
+ /**
87
+ * Requests testnet USDC from the CDP faucet (only works on testnets).
88
+ */
89
+ async faucetUsdc(address) {
90
+ if (this.network === "base-mainnet") {
91
+ throw new Error("Faucet is only available on testnets.");
92
+ }
93
+ const res = await this.client.evm.requestFaucet({
94
+ address,
95
+ network: "base-sepolia",
96
+ token: "usdc",
97
+ });
98
+ return res.transactionHash;
99
+ }
100
+ /**
101
+ * Waits for a transaction to reach finality on the configured network.
102
+ */
103
+ async waitForReceipt(txHash) {
104
+ return this.publicClient.waitForTransactionReceipt({ hash: txHash });
105
+ }
106
+ /**
107
+ * Gets the USDC balance for a wallet address.
108
+ */
109
+ async getUsdcBalance(address) {
110
+ const usdc = USDC_ADDRESS[this.network];
111
+ if (!usdc)
112
+ throw new Error(`USDC not configured for network ${this.network}`);
113
+ const balance = await this.publicClient.readContract({
114
+ address: usdc,
115
+ abi: ERC20_BALANCE_ABI,
116
+ functionName: "balanceOf",
117
+ args: [address],
118
+ });
119
+ return formatUnits(balance, 6);
120
+ }
121
+ /**
122
+ * Gets the native ETH balance for a wallet address (for gas).
123
+ */
124
+ async getNativeBalance(address) {
125
+ const balance = await this.publicClient.getBalance({ address });
126
+ return formatUnits(balance, 18);
127
+ }
128
+ /**
129
+ * Sends a USDC payment from a wallet address to a recipient.
130
+ * Returns the transaction hash.
131
+ */
132
+ async sendUsdcPayment(params) {
133
+ const usdc = USDC_ADDRESS[this.network];
134
+ if (!usdc)
135
+ throw new Error(`USDC not configured for network ${this.network}`);
136
+ const amount = parseUnits(params.amount, 6);
137
+ const data = encodeFunctionData({
138
+ abi: ERC20_TRANSFER_ABI,
139
+ functionName: "transfer",
140
+ args: [params.to, amount],
141
+ });
142
+ const res = await this.client.evm.sendTransaction({
143
+ address: params.from,
144
+ transaction: {
145
+ to: usdc,
146
+ data,
147
+ value: BigInt(0),
148
+ },
149
+ network: this.network,
150
+ });
151
+ return {
152
+ transactionHash: res.transactionHash,
153
+ network: this.network,
154
+ };
155
+ }
156
+ /**
157
+ * Funds a wallet for the first time on testnets: USDC if below 1, ETH gas if below 0.001.
158
+ * Skips both if already funded. Fails silently on faucet rate-limits.
159
+ * @returns true if the wallet holds >= 1 USDC after this call.
160
+ */
161
+ async fundOnFirstSend(address) {
162
+ if (this.network === "base-mainnet")
163
+ return true;
164
+ try {
165
+ const usdc = Number(await this.getUsdcBalance(address));
166
+ if (usdc < 1) {
167
+ const hash = await this.faucetUsdc(address);
168
+ await this.waitForReceipt(hash);
169
+ }
170
+ // Poll until USDC is visible on-chain (faucet credit and CDP's view lag on new wallets).
171
+ await applyTimeout(15000);
172
+ for (let i = 0; i < 12; i++) {
173
+ if (Number(await this.getUsdcBalance(address)) >= 1)
174
+ break;
175
+ await applyTimeout(5000);
176
+ }
177
+ }
178
+ catch {
179
+ // faucet rate-limited — fall through, caller retries
180
+ }
181
+ try {
182
+ const eth = Number(await this.getNativeBalance(address));
183
+ if (eth < 0.001) {
184
+ const hash = await this.faucetEth(address);
185
+ await this.waitForReceipt(hash);
186
+ await applyTimeout(5000);
187
+ }
188
+ }
189
+ catch {
190
+ // ETH faucet rate-limited — fall through
191
+ }
192
+ return Number(await this.getUsdcBalance(address)) >= 1;
193
+ }
194
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/smoke.js ADDED
@@ -0,0 +1,25 @@
1
+ import { ConfidentialPay } from "./index.js";
2
+ import dotenv from "dotenv";
3
+ dotenv.config();
4
+ async function main() {
5
+ const pay = new ConfidentialPay();
6
+ console.log(`[smoke] network: ${pay.networkName}`);
7
+ console.log("[smoke] creating wallet...");
8
+ const wallet = await pay.getOrCreateWallet("confidential-pay-smoke");
9
+ console.log(`[smoke] wallet: ${wallet.address}`);
10
+ if (pay.networkName !== "base-mainnet") {
11
+ console.log("[smoke] fauceting ETH...");
12
+ const hash = await pay.faucetEth(wallet.address);
13
+ console.log(`[smoke] faucet tx: ${hash}`);
14
+ console.log("[smoke] waiting for faucet receipt...");
15
+ const receipt = await pay.waitForReceipt(hash);
16
+ console.log(`[smoke] faucet status: ${receipt.status}`);
17
+ const usdcBalance = await pay.getUsdcBalance(wallet.address);
18
+ console.log(`[smoke] usdc balance: ${usdcBalance}`);
19
+ }
20
+ console.log("[smoke] DONE — credentials valid, wallet created.");
21
+ }
22
+ main().catch((err) => {
23
+ console.error("[smoke] FAILED:", err.message ?? err);
24
+ process.exit(1);
25
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import { ConfidentialPay } from "./index.js";
2
+ import dotenv from "dotenv";
3
+ dotenv.config();
4
+ async function main() {
5
+ const pay = new ConfidentialPay();
6
+ console.log(`[transfer] network: ${pay.networkName}`);
7
+ const sender = await pay.getOrCreateWallet("sender");
8
+ const receiver = await pay.getOrCreateWallet("receiver");
9
+ console.log(`[transfer] sender: ${sender.address}`);
10
+ console.log(`[transfer] receiver: ${receiver.address}`);
11
+ console.log("[transfer] fauceting ETH + USDC...");
12
+ try {
13
+ const ethHash = await pay.faucetEth(sender.address);
14
+ console.log(`[transfer] ETH faucet tx: ${ethHash}`);
15
+ await pay.waitForReceipt(ethHash);
16
+ }
17
+ catch (e) {
18
+ console.log(`[transfer] ETH faucet skipped: ${e.message ?? e}`);
19
+ }
20
+ const senderBal = await pay.getUsdcBalance(sender.address);
21
+ console.log(`[transfer] sender USDC before: ${senderBal}`);
22
+ if (Number(senderBal) < 1) {
23
+ try {
24
+ const hash = await pay.faucetUsdc(sender.address);
25
+ console.log(`[transfer] faucet tx: ${hash}`);
26
+ await pay.waitForReceipt(hash);
27
+ }
28
+ catch (e) {
29
+ console.log(`[transfer] faucet skipped: ${e.message ?? e}`);
30
+ return;
31
+ }
32
+ }
33
+ // Faucet balance can lag the receipt — poll up to 30s.
34
+ let received = "0";
35
+ for (let i = 0; i < 6; i++) {
36
+ received = await pay.getUsdcBalance(sender.address);
37
+ if (Number(received) > 0)
38
+ break;
39
+ await new Promise((s) => setTimeout(s, 5000));
40
+ }
41
+ console.log(`[transfer] sender USDC after faucet: ${received}`);
42
+ console.log("[transfer] sending 0.5 USDC...");
43
+ const tx = await pay.sendUsdcPayment({
44
+ from: sender.address,
45
+ to: receiver.address,
46
+ amount: "0.5",
47
+ });
48
+ console.log(`[transfer] tx: ${tx.transactionHash}`);
49
+ const receipt = await pay.waitForReceipt(tx.transactionHash);
50
+ console.log(`[transfer] status: ${receipt.status}`);
51
+ const senderAfter = await pay.getUsdcBalance(sender.address);
52
+ const receiverAfter = await pay.getUsdcBalance(receiver.address);
53
+ console.log(`[transfer] sender USDC after: ${senderAfter}`);
54
+ console.log(`[transfer] receiver USDC after: ${receiverAfter}`);
55
+ if (Number(receiverAfter) > 0) {
56
+ console.log("[transfer] SUCCESS — USDC transfer confirmed on Base Sepolia");
57
+ }
58
+ else {
59
+ console.log("[transfer] receiver balance unchanged — investigate");
60
+ }
61
+ }
62
+ main().catch((err) => {
63
+ console.error("[transfer] FAILED:", err.message ?? err);
64
+ process.exit(1);
65
+ });
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@bvdaniel/confidential-pay-core",
3
+ "version": "0.1.0",
4
+ "description": "Core wrapper around CDP Non-custodial Wallets for confidential USDC payments on Base.",
5
+ "keywords": [
6
+ "confidential",
7
+ "payments",
8
+ "usdc",
9
+ "base",
10
+ "cdp",
11
+ "wallets",
12
+ "crypto"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "a0x-co <hi@a0x.co>",
16
+ "homepage": "https://a0x-confidential-pay-demo-2w42vltmu-bvdaniels-projects.vercel.app",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/a0x-co/a0x-confidential-pay.git",
20
+ "directory": "packages/core"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/a0x-co/a0x-confidential-pay/issues"
24
+ },
25
+ "type": "module",
26
+ "main": "./dist/index.js",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "peerDependencies": {
39
+ "@coinbase/cdp-sdk": "^0.9.0"
40
+ },
41
+ "dependencies": {
42
+ "dotenv": "^17.4.2",
43
+ "viem": "^2.56.1"
44
+ },
45
+ "devDependencies": {
46
+ "@coinbase/cdp-sdk": "^1.55.0",
47
+ "@types/node": "^22",
48
+ "typescript": "^5"
49
+ },
50
+ "scripts": {
51
+ "build": "tsc -p tsconfig.json",
52
+ "smoke": "node --env-file=.env dist/smoke.js",
53
+ "test:transfer": "node --env-file=.env dist/transfer-test.js",
54
+ "test": "node --test dist/test/*.test.js"
55
+ }
56
+ }