@algopayoracle/oracle-sdk 1.0.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 ADDED
@@ -0,0 +1,397 @@
1
+ # @algopayoracle/oracle-sdk
2
+
3
+ **Programmable payment oracle for Algorand.**
4
+ Bridge any fiat payment to an on-chain action — verified by Ed25519 signature, enforced by smart contract.
5
+
6
+ ```bash
7
+ npm install @algopayoracle/oracle-sdk
8
+ ```
9
+
10
+ ---
11
+
12
+ ## What it does
13
+
14
+ When a user pays via UPI, Razorpay, Stripe, or any other gateway, your backend receives a payment event. This SDK signs that event with an oracle key, submits it to an Algorand smart contract, and the contract independently verifies the signature before executing any action.
15
+
16
+ **The contract does not trust your backend. It only trusts the cryptographic proof.**
17
+
18
+ ```
19
+ Payment Gateway → Webhook → Oracle Signs → Algorand Contract → Action Executed
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Quickstart
25
+
26
+ ```js
27
+ const { AlgoPayClient } = require("@algopayoracle/oracle-sdk");
28
+
29
+ const client = new AlgoPayClient({
30
+ mnemonic: process.env.ORACLE_MNEMONIC,
31
+ network: "testnet",
32
+ appId: Number(process.env.ALGO_APP_ID),
33
+ });
34
+
35
+ // Call this from any payment webhook — gateway-agnostic
36
+ const result = await client.verifyAndCommit({
37
+ payment_id: "your_gateway_payment_id",
38
+ amount: 100,
39
+ currency: "INR",
40
+ action: "unlock",
41
+ provider: "razorpay", // optional — enables namespaced replay protection
42
+ });
43
+
44
+ console.log(result.txId); // confirmed Algorand transaction ID
45
+ console.log(result.explorerUrl); // Lora explorer link
46
+ console.log(result.apc1); // APC-1 standardized credential
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Core concepts
52
+
53
+ ### Payment-gateway agnostic
54
+
55
+ The oracle pipeline speaks `PaymentEvent`, not Razorpay or Stripe:
56
+
57
+ ```js
58
+ // This shape works from any gateway
59
+ const event = {
60
+ payment_id: "your_gateway_id",
61
+ amount: 100, // integer, base currency unit
62
+ currency: "INR", // ISO 4217
63
+ action: "unlock", // what to trigger on-chain
64
+ provider: "razorpay", // optional label
65
+ };
66
+
67
+ const result = await client.verifyAndCommit(event);
68
+ ```
69
+
70
+ Adapters handle gateway-specific signature verification and normalization.
71
+ The oracle and contract have zero gateway-specific code.
72
+
73
+ ### APC-1 — standardized payment credential
74
+
75
+ Every verified payment produces an APC-1 credential:
76
+
77
+ ```json
78
+ {
79
+ "apc": "1",
80
+ "payment_id": "pay_XXXXXXX",
81
+ "canonical_id": "razorpay:pay_XXXXXXX",
82
+ "amount": XXX,
83
+ "currency": "INR",
84
+ "action": "unlock",
85
+ "timestamp": 1714500000,
86
+ "oracle_address": "ABCDEF...",
87
+ "signature": "base64...",
88
+ "chain": "algorand",
89
+ "network": "testnet",
90
+ "app_id": XXXXXXXXX,
91
+ "provider": "razorpay"
92
+ }
93
+ ```
94
+
95
+ APC-1 proofs are self-contained and verifiable by anyone who knows the oracle's public key.
96
+
97
+ ### Trust model
98
+
99
+ - Oracle's Ed25519 public key is registered in the contract at deploy time
100
+ - The contract runs `ed25519verify_bare` on every call — no valid signature, no action
101
+ - Multiple oracles can be registered (rotation without downtime)
102
+ - `payment_id` box storage prevents replay attacks on-chain
103
+ - Proofs are time-bound — valid for 5 minutes from signing
104
+
105
+ ---
106
+
107
+ ## API reference
108
+
109
+ ### `AlgoPayClient`
110
+
111
+ #### Constructor
112
+
113
+ ```js
114
+ new AlgoPayClient({
115
+ mnemonic, // required — 25-word oracle account mnemonic
116
+ network, // "localnet" | "testnet" | "mainnet" (default: "testnet")
117
+ appId, // deployed AlgoPayOracle App ID (null = anchor mode)
118
+ algod, // optional — custom algosdk.Algodv2 instance
119
+ indexer, // optional — custom algosdk.Indexer instance
120
+ explorerBase, // optional — custom explorer URL
121
+ })
122
+ ```
123
+
124
+ #### `verifyAndCommit(payment)` → `Promise<Result>`
125
+
126
+ Sign and commit a payment proof to Algorand.
127
+
128
+ ```js
129
+ const result = await client.verifyAndCommit({
130
+ payment_id: "pay_XXXXXXX",
131
+ amount: 100,
132
+ currency: "INR",
133
+ action: "unlock",
134
+ provider: "razorpay",
135
+ });
136
+ // result: { txId, proof, apc1, explorerUrl, verifyUrl, access_seconds }
137
+ ```
138
+
139
+ #### `verifyProof(txId)` → `Promise<VerifyResult>`
140
+
141
+ Verify a proof via the Algorand indexer.
142
+
143
+ ```js
144
+ const { valid, proof } = await client.verifyProof("TXID...");
145
+ ```
146
+
147
+ #### `verifyProofOffchain(proof)` → `VerifyResult`
148
+
149
+ Verify a proof's Ed25519 signature without any network call.
150
+
151
+ ```js
152
+ const { valid } = client.verifyProofOffchain(result.proof);
153
+ ```
154
+
155
+ #### Oracle rotation
156
+
157
+ ```js
158
+ await client.addOracle("ALGORAND_ADDRESS_OR_BASE64_PUBKEY"); // creator only
159
+ await client.removeOracle("..."); // cannot remove last oracle
160
+ const registered = await client.isOracleRegistered("...");
161
+
162
+ const total = await client.getTotalVerified();
163
+ const count = await client.getOracleCount();
164
+ ```
165
+
166
+ ---
167
+
168
+ ### `OracleSigner`
169
+
170
+ Pure Ed25519 signing — no network calls. Useful for offline signing and testing.
171
+
172
+ ```js
173
+ const { OracleSigner } = require("@algopayoracle/oracle-sdk");
174
+
175
+ const signer = new OracleSigner(mnemonic);
176
+
177
+ console.log(signer.getAddress()); // Algorand address
178
+ console.log(signer.getPublicKeyBase64()); // paste into contract deploy
179
+
180
+ const proof = signer.sign({ payment_id, amount, action, currency, provider });
181
+ const valid = OracleSigner.verifyOffchain(proof); // static, no network
182
+ ```
183
+
184
+ ---
185
+
186
+ ### `ProofVerifier`
187
+
188
+ ```js
189
+ const { ProofVerifier, createClients } = require("@algopayoracle/oracle-sdk");
190
+
191
+ const { indexer } = createClients("testnet");
192
+ const verifier = new ProofVerifier({ indexer, network: "testnet" });
193
+
194
+ // Single txId
195
+ const result = await verifier.verifyTxn("TXID...", {
196
+ expectedOracleAddress: "ABCDEF...", // optional — restrict to specific oracle
197
+ expectedAction: "unlock", // optional — restrict to specific action
198
+ maxAgeSecs: 300, // optional — default 300
199
+ });
200
+
201
+ // Batch
202
+ const results = await verifier.verifyBatch(["TXID1", "TXID2", "TXID3"]);
203
+ ```
204
+
205
+ ---
206
+
207
+ ### Payment adapters
208
+
209
+ Adapters are optional. You can normalize any gateway's webhook payload manually.
210
+
211
+ #### Razorpay
212
+
213
+ ```js
214
+ const { RazorpayAdapter } = require("@algopayoracle/oracle-sdk");
215
+
216
+ // Share orderStore with the client to enforce server-side amounts
217
+ const orderStore = new Map();
218
+ const adapter = new RazorpayAdapter({
219
+ keyId: process.env.RAZORPAY_KEY_ID,
220
+ keySecret: process.env.RAZORPAY_KEY_SECRET,
221
+ orderStore, // prevents client from spoofing amounts
222
+ });
223
+
224
+ // Server-side webhook
225
+ app.post("/webhook/razorpay", (req, res) => {
226
+ const event = adapter.parseWebhook(req.rawBody, req.headers["x-razorpay-signature"]);
227
+ if (!event) return res.status(401).end();
228
+ const result = await client.verifyAndCommit(event);
229
+ res.json({ txId: result.txId });
230
+ });
231
+
232
+ // Create order (stores amount server-side)
233
+ const order = await adapter.createOrder({ amount: 100, currency: "INR" });
234
+
235
+ // Client-side verification (amount taken from orderStore, not request body)
236
+ app.post("/verify-payment", (req, res) => {
237
+ const event = adapter.parseClientPayment({
238
+ razorpay_order_id: req.body.razorpay_order_id,
239
+ razorpay_payment_id: req.body.razorpay_payment_id,
240
+ razorpay_signature: req.body.razorpay_signature,
241
+ action: "unlock",
242
+ });
243
+ const result = await client.verifyAndCommit(event);
244
+ res.json({ txId: result.txId });
245
+ });
246
+ ```
247
+
248
+ #### Stripe
249
+
250
+ ```js
251
+ const { StripeAdapter } = require("@algopayoracle/oracle-sdk");
252
+ // npm install stripe
253
+
254
+ const adapter = new StripeAdapter({
255
+ webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
256
+ secretKey: process.env.STRIPE_SECRET_KEY,
257
+ });
258
+
259
+ app.post("/webhook/stripe", (req, res) => {
260
+ const event = adapter.parseWebhook(req.rawBody, req.headers["stripe-signature"]);
261
+ if (!event) return res.status(401).end();
262
+ const result = await client.verifyAndCommit(event);
263
+ res.json({ txId: result.txId });
264
+ });
265
+ ```
266
+
267
+ #### Custom gateway (any provider)
268
+
269
+ ```js
270
+ // No adapter needed — normalize manually and call verifyAndCommit
271
+ app.post("/webhook/payu", async (req, res) => {
272
+ // Verify PayU's signature with their method
273
+ if (!verifyPayUSignature(req.rawBody, req.headers["x-payu-checksum"])) {
274
+ return res.status(401).end();
275
+ }
276
+ const result = await client.verifyAndCommit({
277
+ payment_id: req.body.mihpayid,
278
+ amount: Math.round(Number(req.body.amount)),
279
+ currency: "INR",
280
+ action: "unlock",
281
+ provider: "payu",
282
+ });
283
+ res.json({ txId: result.txId });
284
+ });
285
+ ```
286
+
287
+ ---
288
+
289
+ ## Contract deployment
290
+
291
+ ### 1. Get your oracle public key
292
+
293
+ ```bash
294
+ node -e "
295
+ const { OracleSigner } = require('@algopayoracle/oracle-sdk');
296
+ const s = new OracleSigner(process.env.ORACLE_MNEMONIC);
297
+ console.log('Address:', s.getAddress());
298
+ console.log('Pubkey :', s.getPublicKeyBase64());
299
+ "
300
+ ```
301
+
302
+ ### 2. Compile the contract
303
+
304
+ ```bash
305
+ cd contracts
306
+ algokit compile python AlgoPayOracle.py
307
+ ```
308
+
309
+ ### 3. Deploy via Lora
310
+
311
+ Open https://lora.algokit.io/testnet → App Lab → Create → upload the compiled ARC-32 JSON.
312
+
313
+ When prompted for `create()` args, pass the oracle's 32-byte pubkey (base64-decoded).
314
+
315
+ ### 4. Fund the contract for box storage
316
+
317
+ Each payment creates a box (~33 bytes) costing ~0.01109 ALGO.
318
+ Send at least 0.1 ALGO to the contract address after deploy.
319
+
320
+ ### 5. Configure environment
321
+
322
+ ```env
323
+ ORACLE_MNEMONIC=your twenty five words here
324
+ ALGO_NETWORK=testnet
325
+ ALGO_APP_ID=XXXXXXXXX
326
+ ADMIN_API_KEY=your_secret_admin_key
327
+ ALLOWED_ORIGINS=https://yourdomain.com
328
+ RAZORPAY_KEY_ID=rzp_test_xxx # optional
329
+ RAZORPAY_KEY_SECRET=your_secret # optional
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Network configuration
335
+
336
+ ### Built-in networks (AlgoNode, no API key required)
337
+
338
+ | Network | Algod | Indexer |
339
+ |-----------|----------------------------------------|-----------------------------------------|
340
+ | localnet | http://localhost:4001 | http://localhost:8980 |
341
+ | testnet | https://testnet-api.algonode.cloud | https://testnet-idx.algonode.cloud |
342
+ | mainnet | https://mainnet-api.algonode.cloud | https://mainnet-idx.algonode.cloud |
343
+
344
+ ### Custom node (Nodely, PureStake, self-hosted)
345
+
346
+ ```js
347
+ const { createCustomClients } = require("@algopayoracle/oracle-sdk");
348
+
349
+ const { algod, indexer } = createCustomClients({
350
+ algodUrl: "https://mainnet-api.nodely.dev",
351
+ algodToken: process.env.NODELY_TOKEN,
352
+ indexerUrl: "https://mainnet-idx.nodely.dev",
353
+ indexerToken: process.env.NODELY_TOKEN,
354
+ explorerBase: "https://lora.algokit.io/mainnet",
355
+ });
356
+
357
+ const client = new AlgoPayClient({ mnemonic, appId, algod, indexer });
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Security notes
363
+
364
+ - **Admin endpoints** — `addOracle` / `removeOracle` call on-chain contract functions. In production, these routes must be protected (API key at minimum) and should not be on the public internet. See the express-webhook example for the `requireAdmin` middleware pattern.
365
+ - **Amount trust** — Never trust `amount` from the client in the payment verify path. The `RazorpayAdapter` orderStore pattern enforces this. If you write a custom adapter, always source the amount from your server-side order record or the provider's webhook body.
366
+ - **Oracle key custody** — The oracle key is the trust anchor. Treat it like a private key: never commit to git, rotate via `addOracle` + `removeOracle` if compromised, consider multisig for mainnet.
367
+ - **Webhook body size** — Always apply a body size limit to webhook handlers. The express-webhook example enforces 512 KB.
368
+ - **CORS** — Lock `ALLOWED_ORIGINS` to your actual frontend domain before production deployment.
369
+
370
+ ---
371
+
372
+ ## Error handling
373
+
374
+ ```js
375
+ const {
376
+ AlgoPayError,
377
+ InsufficientAmountError,
378
+ ProofExpiredError,
379
+ OracleNotRegisteredError,
380
+ ReplayError,
381
+ ProviderAuthError,
382
+ } = require("@algopayoracle/oracle-sdk");
383
+
384
+ try {
385
+ await client.verifyAndCommit(event);
386
+ } catch (e) {
387
+ if (e instanceof InsufficientAmountError) { /* amount < minimum */ }
388
+ if (e instanceof ProviderAuthError) { /* gateway sig check failed */ }
389
+ if (e instanceof AlgoPayError) { /* any SDK error */ }
390
+ }
391
+ ```
392
+
393
+ ---
394
+
395
+ ## License
396
+
397
+ MIT
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@algopayoracle/oracle-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Programmable payment oracle SDK \u2014 bridge fiat payments to Algorand smart contracts",
5
+ "main": "src/index.js",
6
+ "files": [
7
+ "src",
8
+ "README.md"
9
+ ],
10
+ "scripts": {
11
+ "test": "node --test examples/quickstart.js",
12
+ "test:unit": "node --test",
13
+ "test:integration": "ALGO_NETWORK=localnet node --test",
14
+ "example": "node examples/express-webhook.js",
15
+ "lint": "node --check src/index.js src/AlgoPayClient.js src/OracleSigner.js",
16
+ "prepublishOnly": "node --check src/index.js && echo 'Pre-publish checks passed'"
17
+ },
18
+ "keywords": [
19
+ "algorand",
20
+ "oracle",
21
+ "payments",
22
+ "upi",
23
+ "razorpay",
24
+ "web3",
25
+ "blockchain",
26
+ "ed25519",
27
+ "smart-contract"
28
+ ],
29
+ "author": "AlgoPay Oracle",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/your-org/algopay-oracle-sdk"
34
+ },
35
+ "peerDependencies": {
36
+ "algosdk": ">=3.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "algosdk": "^3.0.0",
40
+ "express": "^4.18.0",
41
+ "express-rate-limit": "^7.0.0",
42
+ "dotenv": "^16.0.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=18.0.0"
46
+ },
47
+ "types": "src/index.d.ts",
48
+ "exports": {
49
+ ".": {
50
+ "require": "./src/index.js",
51
+ "types": "./src/index.d.ts"
52
+ },
53
+ "./validate": {
54
+ "require": "./src/validate.js"
55
+ },
56
+ "./errors": {
57
+ "require": "./src/errors.js"
58
+ }
59
+ }
60
+ }