@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 +117 -0
- package/dist/chunk-SKFD6TSD.mjs +266 -0
- package/dist/index.d.mts +1083 -0
- package/dist/index.d.ts +1083 -0
- package/dist/index.js +3581 -0
- package/dist/index.mjs +3317 -0
- package/dist/tableland.d.mts +46 -0
- package/dist/tableland.d.ts +46 -0
- package/dist/tableland.js +268 -0
- package/dist/tableland.mjs +6 -0
- package/package.json +74 -0
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
|
+
};
|