@arcenpay/node 0.0.1

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 ADDED
@@ -0,0 +1,117 @@
1
+ # @arcenpay/node
2
+
3
+ ArcenPay Node.js SDK for server-side billing actions, access-token minting, company APIs, entitlement checks, subscription activation, webhook verification, and optional advanced runtime services.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @arcenpay/node
9
+ ```
10
+
11
+ ## Standard server path
12
+
13
+ Create the client:
14
+
15
+ ```ts
16
+ import { ArcenClient } from "@arcenpay/node";
17
+
18
+ const client = new ArcenClient({
19
+ apiKey: process.env.ARCENPAY_API_KEY!,
20
+ });
21
+ ```
22
+
23
+ Mint a short-lived access token:
24
+
25
+ ```ts
26
+ const session = await client.identify({
27
+ company: { id: "company_123", wallet: "0xabc..." },
28
+ user: { id: "user_123", wallet: "0xabc..." },
29
+ expiresIn: 3600,
30
+ });
31
+ ```
32
+
33
+ Check access and consume usage:
34
+
35
+ ```ts
36
+ const entitlement = await client.checkEntitlement("scan", {
37
+ id: "company_123",
38
+ });
39
+ const flag = await client.checkFlag("scan", { id: "company_123" });
40
+
41
+ const result = await client.consumeEntitlement({
42
+ featureKey: "scan",
43
+ company: { id: "company_123" },
44
+ idempotencyKey: "scan-req-1",
45
+ });
46
+ ```
47
+
48
+ Activate a subscription:
49
+
50
+ ```ts
51
+ const activation = await client.activateSubscription({
52
+ planId: "starter",
53
+ paymentAccount: "0xabc...",
54
+ company: { id: "company_123", wallet: "0xabc..." },
55
+ });
56
+ ```
57
+
58
+ Verify webhook signatures:
59
+
60
+ ```ts
61
+ import { verifyWebhookSignature } from "@arcenpay/node";
62
+
63
+ const rawBody = await req.text();
64
+ const signatureHeader = req.headers.get("x-meap-signature") ?? "";
65
+ const signature = signatureHeader.startsWith("sha256=")
66
+ ? signatureHeader.slice("sha256=".length)
67
+ : signatureHeader;
68
+
69
+ if (
70
+ !verifyWebhookSignature(
71
+ rawBody,
72
+ signature,
73
+ process.env.ARCENPAY_WEBHOOK_SECRET!,
74
+ )
75
+ ) {
76
+ throw new Error("Invalid webhook signature");
77
+ }
78
+ ```
79
+
80
+ ## Main exports
81
+
82
+ Standard app integrations usually use:
83
+
84
+ - `ArcenClient`
85
+ - `ArcenApiError`
86
+ - `verifyWebhookSignature`
87
+
88
+ The package also exposes advanced optional services:
89
+
90
+ - `x402Middleware`
91
+ - `WebhookService`
92
+ - `UsageService`
93
+ - `SettlementService`
94
+ - `SettlementWriterService`
95
+ - `EventListenerService`
96
+ - `BillingKeeper`
97
+ - `TablelandService`
98
+ - `ProofOrchestrator`
99
+ - `AxelarTransport`
100
+ - `CCIPTransport`
101
+ - `InMemoryNonceStore`
102
+ - `RedisNonceStore`
103
+
104
+ ## Environment variables
105
+
106
+ ```bash
107
+ ARCENPAY_API_KEY=api_xxxxxxxxx
108
+ ARCENPAY_WEBHOOK_SECRET=whsec_xxxxxxxxx
109
+ ```
110
+
111
+ `ARCENPAY_BASE_URL` is optional. The SDK defaults to `http://localhost:3000`
112
+ outside production and `https://app.arcenpay.com` in production.
113
+
114
+ ## Boundary
115
+
116
+ Use `@arcenpay/node` for server-owned logic. For customer-facing UI, use
117
+ `@arcenpay/react`.
@@ -0,0 +1,266 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/services/tableland.ts
23
+ import { Database } from "@tableland/sdk";
24
+ import { JsonRpcProvider, Wallet } from "ethers";
25
+ var TablelandService = class {
26
+ config;
27
+ initialized = false;
28
+ dbRead = null;
29
+ dbWrite = null;
30
+ constructor(config) {
31
+ this.config = config;
32
+ }
33
+ async initialize() {
34
+ this.ensureSafeTableName();
35
+ this.dbRead = new Database({
36
+ ...this.config.gatewayUrl ? { baseUrl: this.config.gatewayUrl } : {}
37
+ });
38
+ if (this.config.privateKey && this.config.rpcUrl) {
39
+ const provider = new JsonRpcProvider(this.config.rpcUrl);
40
+ const signer = new Wallet(this.config.privateKey, provider);
41
+ this.dbWrite = new Database({
42
+ signer,
43
+ ...this.config.gatewayUrl ? { baseUrl: this.config.gatewayUrl } : {}
44
+ });
45
+ }
46
+ this.initialized = true;
47
+ console.log(
48
+ `[Tableland] Initialized table=${this.config.tableName} mode=${this.dbWrite ? "read-write" : "read-only"}`
49
+ );
50
+ }
51
+ async getFeatureFlags(walletAddress) {
52
+ this.ensureInitialized();
53
+ const normalized = walletAddress.toLowerCase();
54
+ try {
55
+ const statement = `SELECT feature_flags FROM ${this.config.tableName} WHERE wallet_address = ? LIMIT 1`;
56
+ const response = await this.dbRead.prepare(statement).bind(normalized).all();
57
+ const row = response.results[0];
58
+ if (!row?.feature_flags) return {};
59
+ const parsed = JSON.parse(row.feature_flags);
60
+ return parsed ?? {};
61
+ } catch (err) {
62
+ console.error("[Tableland] getFeatureFlags error:", err);
63
+ return {};
64
+ }
65
+ }
66
+ async setFeatureFlags(walletAddress, flags, metadata) {
67
+ this.ensureInitialized();
68
+ this.ensureWritable();
69
+ const normalized = walletAddress.toLowerCase();
70
+ const now = Math.floor(Date.now() / 1e3);
71
+ const nftTokenId = metadata.nft_token_id ?? 0;
72
+ const planTier = metadata.plan_tier ?? "starter";
73
+ const apiRateLimit = metadata.api_rate_limit ?? this.defaultRateLimitForTier(planTier);
74
+ const billingInterval = metadata.billing_interval ?? 2592e3;
75
+ const lastRenewed = metadata.last_renewed ?? now;
76
+ const expiresAt = metadata.expires_at ?? 0;
77
+ const chainId = metadata.chain_id ?? this.config.chainId;
78
+ try {
79
+ const statement = `
80
+ INSERT INTO ${this.config.tableName}
81
+ (wallet_address, nft_token_id, plan_tier, feature_flags, api_rate_limit, billing_interval, last_renewed, expires_at, chain_id)
82
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
83
+ ON CONFLICT (wallet_address) DO UPDATE SET
84
+ nft_token_id = excluded.nft_token_id,
85
+ plan_tier = excluded.plan_tier,
86
+ feature_flags = excluded.feature_flags,
87
+ api_rate_limit = excluded.api_rate_limit,
88
+ billing_interval = excluded.billing_interval,
89
+ last_renewed = excluded.last_renewed,
90
+ expires_at = excluded.expires_at,
91
+ chain_id = excluded.chain_id
92
+ `;
93
+ await this.dbWrite.prepare(statement).bind(
94
+ normalized,
95
+ nftTokenId,
96
+ planTier,
97
+ JSON.stringify(flags),
98
+ apiRateLimit,
99
+ billingInterval,
100
+ lastRenewed,
101
+ expiresAt,
102
+ chainId
103
+ ).run();
104
+ return true;
105
+ } catch (err) {
106
+ console.error("[Tableland] setFeatureFlags error:", err);
107
+ return false;
108
+ }
109
+ }
110
+ async revokeEntitlement(walletAddress) {
111
+ this.ensureInitialized();
112
+ this.ensureWritable();
113
+ try {
114
+ const statement = `DELETE FROM ${this.config.tableName} WHERE wallet_address = ?`;
115
+ await this.dbWrite.prepare(statement).bind(walletAddress.toLowerCase()).run();
116
+ return true;
117
+ } catch (err) {
118
+ console.error("[Tableland] revokeEntitlement error:", err);
119
+ return false;
120
+ }
121
+ }
122
+ async revokeEntitlementByTokenId(tokenId) {
123
+ this.ensureInitialized();
124
+ this.ensureWritable();
125
+ try {
126
+ const statement = `DELETE FROM ${this.config.tableName} WHERE nft_token_id = ?`;
127
+ await this.dbWrite.prepare(statement).bind(tokenId).run();
128
+ return true;
129
+ } catch (err) {
130
+ console.error("[Tableland] revokeEntitlementByTokenId error:", err);
131
+ return false;
132
+ }
133
+ }
134
+ async updateEntitlementByTokenId(tokenId, updates) {
135
+ this.ensureInitialized();
136
+ this.ensureWritable();
137
+ const assignments = [];
138
+ const values = [];
139
+ if (updates.feature_flags) {
140
+ assignments.push("feature_flags = ?");
141
+ values.push(JSON.stringify(updates.feature_flags));
142
+ }
143
+ if (updates.plan_tier) {
144
+ assignments.push("plan_tier = ?");
145
+ values.push(updates.plan_tier);
146
+ }
147
+ if (updates.expires_at !== void 0) {
148
+ assignments.push("expires_at = ?");
149
+ values.push(updates.expires_at);
150
+ }
151
+ if (updates.last_renewed !== void 0) {
152
+ assignments.push("last_renewed = ?");
153
+ values.push(updates.last_renewed);
154
+ }
155
+ if (updates.api_rate_limit !== void 0) {
156
+ assignments.push("api_rate_limit = ?");
157
+ values.push(updates.api_rate_limit);
158
+ }
159
+ if (updates.billing_interval !== void 0) {
160
+ assignments.push("billing_interval = ?");
161
+ values.push(updates.billing_interval);
162
+ }
163
+ if (assignments.length === 0) return true;
164
+ try {
165
+ const statement = `UPDATE ${this.config.tableName} SET ${assignments.join(", ")} WHERE nft_token_id = ?`;
166
+ values.push(tokenId);
167
+ await this.dbWrite.prepare(statement).bind(...values).run();
168
+ return true;
169
+ } catch (err) {
170
+ console.error("[Tableland] updateEntitlementByTokenId error:", err);
171
+ return false;
172
+ }
173
+ }
174
+ async listEntitlements(limit = 100) {
175
+ this.ensureInitialized();
176
+ const safeLimit = Math.max(1, Math.min(limit, 1e3));
177
+ try {
178
+ const statement = `SELECT * FROM ${this.config.tableName} LIMIT ?`;
179
+ const response = await this.dbRead.prepare(statement).bind(safeLimit).all();
180
+ const rows = response.results || [];
181
+ return rows.map((row) => ({
182
+ wallet_address: String(row.wallet_address || ""),
183
+ nft_token_id: Number(row.nft_token_id || 0),
184
+ plan_tier: String(row.plan_tier || "starter"),
185
+ feature_flags: typeof row.feature_flags === "string" ? JSON.parse(row.feature_flags) : row.feature_flags || {},
186
+ api_rate_limit: Number(row.api_rate_limit || 0),
187
+ billing_interval: Number(row.billing_interval || 0),
188
+ last_renewed: Number(row.last_renewed || 0),
189
+ expires_at: Number(row.expires_at || 0),
190
+ chain_id: Number(row.chain_id || this.config.chainId)
191
+ }));
192
+ } catch (err) {
193
+ console.error("[Tableland] listEntitlements error:", err);
194
+ return [];
195
+ }
196
+ }
197
+ async syncSubscription(data) {
198
+ await this.setFeatureFlags(data.walletAddress, data.features, {
199
+ wallet_address: data.walletAddress,
200
+ nft_token_id: data.tokenId,
201
+ plan_tier: data.planTier,
202
+ expires_at: data.expiresAt,
203
+ billing_interval: data.billingInterval,
204
+ last_renewed: Math.floor(Date.now() / 1e3),
205
+ chain_id: this.config.chainId,
206
+ api_rate_limit: this.defaultRateLimitForTier(data.planTier),
207
+ feature_flags: data.features
208
+ });
209
+ }
210
+ async listTierFeatureFlags(providerAddress, tiers = ["starter", "pro", "enterprise"]) {
211
+ const matrix = {};
212
+ for (const tier of tiers) {
213
+ const key = this.buildTierMatrixKey(providerAddress, tier);
214
+ matrix[tier] = await this.getFeatureFlags(key);
215
+ }
216
+ return matrix;
217
+ }
218
+ async setTierFeatureFlags(providerAddress, tier, flags) {
219
+ const key = this.buildTierMatrixKey(providerAddress, tier);
220
+ return this.setFeatureFlags(key, flags, {
221
+ wallet_address: key,
222
+ nft_token_id: 0,
223
+ plan_tier: tier,
224
+ expires_at: 0,
225
+ billing_interval: 0,
226
+ last_renewed: Math.floor(Date.now() / 1e3),
227
+ chain_id: this.config.chainId,
228
+ api_rate_limit: this.defaultRateLimitForTier(tier),
229
+ feature_flags: flags
230
+ });
231
+ }
232
+ buildTierMatrixKey(providerAddress, tier) {
233
+ return `matrix:${providerAddress.toLowerCase()}:${tier.toLowerCase()}`;
234
+ }
235
+ defaultRateLimitForTier(tier) {
236
+ if (tier === "enterprise") return 1e4;
237
+ if (tier === "pro") return 5e3;
238
+ return 1e3;
239
+ }
240
+ ensureInitialized() {
241
+ if (!this.initialized || !this.dbRead) {
242
+ throw new Error("[Tableland] Service not initialized. Call initialize() first.");
243
+ }
244
+ }
245
+ ensureWritable() {
246
+ if (!this.dbWrite) {
247
+ throw new Error(
248
+ "[Tableland] Write requested without signer. Set TABLELAND_PRIVATE_KEY + TABLELAND_RPC_URL."
249
+ );
250
+ }
251
+ }
252
+ ensureSafeTableName() {
253
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(this.config.tableName)) {
254
+ throw new Error(
255
+ `[Tableland] Unsafe table name "${this.config.tableName}". Expected alphanumeric/underscore only.`
256
+ );
257
+ }
258
+ }
259
+ };
260
+
261
+ export {
262
+ __esm,
263
+ __export,
264
+ __toCommonJS,
265
+ TablelandService
266
+ };