dhali-js 1.0.4 → 2.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.
@@ -27,3 +27,6 @@ jobs:
27
27
 
28
28
  - name: Run tests
29
29
  run: npm test
30
+
31
+ - name: Run lint
32
+ run: npx tsc src/dhali/*.js --allowJs --checkJs --noEmit --moduleResolution node --module commonjs --target esnext
package/README.md CHANGED
@@ -4,8 +4,9 @@
4
4
 
5
5
  # dhali-js
6
6
 
7
- A JavaScript library for managing XRPL payment channels and generating auth tokens for use with [Dhali](https://dhali.io) APIs.
8
- Leverages [xrpl.js](https://github.com/XRPLF/xrpl.js) and **only ever performs local signing**—your private key never leaves your environment.
7
+ A JavaScript library for managing payment channels (XRPL & Ethereum) and generating auth tokens for use with [Dhali](https://dhali.io) APIs.
8
+
9
+ Includes support for **Machine-to-Machine (M2M) payments** using seamless off-chain claims.
9
10
 
10
11
  ---
11
12
 
@@ -17,105 +18,112 @@ npm install dhali-js
17
18
 
18
19
  ---
19
20
 
20
- ## Quick Start
21
+ ## Quick Start: Machine-to-Machine Payments
22
+
23
+ ### 1. XRPL
24
+
25
+ Uses `xrpl.js` for local signing.
21
26
 
22
27
  ```js
23
- // ==== 0. Common setup ====
24
- const { Wallet } = require('xrpl')
25
- const { DhaliChannelManager, ChannelNotFound } = require('dhali-js')
28
+ const { Client, Wallet } = require('xrpl')
29
+ const { DhaliChannelManager, ChannelNotFound, Currency } = require('dhali-js')
26
30
 
27
- const seed = "sXXX"
31
+ const seed = "sXXX..."
28
32
  const wallet = Wallet.fromSeed(seed)
29
- const manager = new DhaliChannelManager(wallet)
30
- ```
33
+ const client = new Client("wss://s.altnet.rippletest.net:51233")
34
+ await client.connect()
31
35
 
36
+ const currency = new Currency("XRP", 6)
32
37
 
33
- ### 1. Create a Payment Claim
38
+ // Use Factory
39
+ const manager = DhaliChannelManager.xrpl(wallet, client, "XRPL.TESTNET", currency)
34
40
 
35
- ```js
36
- let token
41
+ // Generate Claim
42
+ let token;
37
43
  try {
38
- token = await manager.getAuthToken()
39
- } catch (err) {
40
- if (err instanceof ChannelNotFound) {
41
- await manager.deposit(1_000_000) // deposit 1 XRP
42
- token = await manager.getAuthToken() // 🔑 regenerate after deposit
43
- } else throw err
44
+ token = await manager.getAuthToken();
45
+ } catch (error) {
46
+ if (error.name === "ChannelNotFound") {
47
+ await manager.deposit(1000000); // Deposit 1 XRP
48
+ token = await manager.getAuthToken();
49
+ } else {
50
+ throw error;
51
+ }
44
52
  }
45
- console.log('New channel token:', token)
46
- ```
47
-
48
- ---
49
-
50
- ### 2. Top Up Later (and Regenerate)
51
-
52
- ```js
53
- await manager.deposit(2_000_000) // add 2 XRP
54
- const updatedToken = await manager.getAuthToken()
55
- console.log('Updated token:', updatedToken)
53
+ console.log('XRPL Token:', token);
56
54
  ```
57
55
 
58
- ---
56
+ ### 2. Ethereum (EVM)
59
57
 
60
- ### 3. Using APIs and Handling 402 "Payment Required" Errors
58
+ Uses `ethers` (v6) for EIP-712 signing.
61
59
 
62
60
  ```js
63
- const fetchWithClaim = async (maxRetries = 5) => {
64
- for (let i = 1; i <= maxRetries; i++) {
65
- const token = await manager.getAuthToken()
66
- const url = `https://xrplcluster.dhali.io?payment-claim=${token}`
67
- const resp = await fetch(url, { /* …RPC call… */ })
68
-
69
- if (resp.status !== 402) return resp.json()
70
-
71
- console.warn(`Attempt ${i}: topping up…`)
72
- await manager.deposit(1_000_000) // deposit 1 XRP
73
- }
74
- throw new Error(`402 after ${maxRetries} retries`)
61
+ const { ethers } = require('ethers')
62
+ const { DhaliChannelManager, getAvailableDhaliCurrencies } = require('dhali-js')
63
+
64
+ // 1. Setup Signer
65
+ const provider = new ethers.JsonRpcProvider("https://rpc.ankr.com/eth_sepolia")
66
+ const signer = new ethers.Wallet("0x...", provider)
67
+
68
+ // 2. Fetch Available Currencies
69
+ const configs = await getAvailableDhaliCurrencies()
70
+ const sepoliaUsdc = configs["SEPOLIA"]["USDC"]
71
+
72
+ // 3. Instantiate Manager with Dynamic Config
73
+ const manager = DhaliChannelManager.evm(
74
+ signer,
75
+ provider,
76
+ "SEPOLIA",
77
+ sepoliaUsdc.currency
78
+ )
79
+
80
+ // 4. Generate Claim
81
+ // 4. Generate Claim
82
+ let token;
83
+ try {
84
+ token = await manager.getAuthToken(1000000); // 1.00 USDC
85
+ } catch (error) {
86
+ if (error.name === "ChannelNotFound") {
87
+ await manager.deposit(1000000); // Deposit 1.00 USDC
88
+ token = await manager.getAuthToken(1000000);
89
+ } else {
90
+ throw error;
91
+ }
75
92
  }
76
-
77
- ;(async () => {
78
- const result = await fetchWithClaim()
79
- console.log(result)
80
- })()
93
+ console.log('EVM Token:', token);
81
94
  ```
82
95
 
83
96
  ---
84
97
 
85
- ## Class reference
98
+ ## Integration
86
99
 
87
- ### `new DhaliChannelManager(wallet: xrpl.Wallet)`
100
+ Pass the token in your API calls to Dhali-enabled services.
88
101
 
89
- * **wallet**: an `xrpl.js` `Wallet` instance (e.g. `Wallet.fromSeed`).
90
-
91
- ---
92
-
93
- ### `async deposit(amountDrops: number) → Promise<object>`
94
-
95
- * **amountDrops**: Number of XRP drops (e.g. `1_000_000` = 1 XRP).
96
- * **Returns**: The JSON result of the `PaymentChannelCreate` or `PaymentChannelFund` transaction.
102
+ ```js
103
+ const url = `https://xrplcluster.dhali.io?payment-claim=${token}`
104
+ const response = await fetch(url, { method: 'POST', body: ... })
105
+ ```
97
106
 
98
107
  ---
99
108
 
100
- ### `async getAuthToken(amountDrops?: number) → Promise<string>`
101
-
102
- * **amountDrops** (optional): How many drops to authorize; defaults to full channel balance.
103
- * **Returns**: A base64-encoded JSON string containing your signed claim.
104
- * **Throws**:
109
+ ## API Reference
105
110
 
106
- * `ChannelNotFound` if there is no open channel.
107
- * `Error` if `amountDrops` exceeds channel capacity.
111
+ ### `DhaliChannelManager`
108
112
 
109
- ---
110
-
111
- ## Errors
113
+ * `.xrpl(wallet, client, protocol, currency)`: Returns `DhaliXrplChannelManager`.
114
+ * `.evm(signer, provider, protocol, currency)`: Returns `DhaliEthChannelManager`.
112
115
 
113
- * **ChannelNotFound**
114
- Thrown when `getAuthToken` finds no channel from your wallet to Dhali’s receiver.
115
-
116
- ---
116
+ ### `getAvailableDhaliCurrencies()`
117
117
 
118
- ## Security
118
+ Returns a Promise resolving to:
119
+ ```js
120
+ {
121
+ "SEPOLIA": {
122
+ "USDC": { currency: ..., destinationAddress: ... },
123
+ ...
124
+ },
125
+ ...
126
+ }
127
+ ```
119
128
 
120
- All XRPL interactions and claim-signatures are done locally via `xrpl.js` + `ripple-keypairs`.
121
- Your private key never leaves your machine.
129
+ ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dhali-js",
3
- "version": "1.0.4",
3
+ "version": "2.0.0",
4
4
  "description": "A JavaScript library for managing XRPL payment channels and generating auth tokens for Dhali APIs",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -16,11 +16,13 @@
16
16
  ],
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
+ "ethers": "^6.0.0",
19
20
  "ripple-keypairs": "^2.0.0",
20
21
  "xrpl": "^4.0.0"
21
22
  },
22
23
  "devDependencies": {
23
- "jest": "^29.0.0",
24
- "prettier": "^3.5.3"
24
+ "jest": "^29.7.0",
25
+ "prettier": "^3.5.3",
26
+ "typescript": "^5.9.3"
25
27
  }
26
28
  }
@@ -0,0 +1,9 @@
1
+ class Currency {
2
+ constructor(code, scale, tokenAddress = null) {
3
+ this.code = code;
4
+ this.scale = scale;
5
+ this.tokenAddress = tokenAddress;
6
+ }
7
+ }
8
+
9
+ module.exports = Currency;
@@ -1,110 +1,39 @@
1
- const { Client } = require("xrpl");
2
- const { buildPaychanAuthHexStringToBeSigned } = require("./createSignedClaim");
3
- const { sign: signClaim } = require("ripple-keypairs");
4
-
5
- class ChannelNotFound extends Error {}
6
-
7
- /**
8
- * A management tool for generating payment claims for use with Dhali APIs.
9
- */
10
- class DhaliChannelManager {
11
- /**
12
- * @param {xrpl.Wallet} wallet
13
- */
14
- constructor(wallet) {
15
- this.client = new Client("wss://s1.ripple.com:51234/");
16
- this.ready = this.client.connect();
17
- this.wallet = wallet;
18
- this.destination = "rLggTEwmTe3eJgyQbCSk4wQazow2TeKrtR";
19
- this.protocol = "XRPL.MAINNET";
20
- }
21
-
22
- async _findChannel() {
23
- await this.ready;
24
- const resp = await this.client.request({
25
- command: "account_channels",
26
- account: this.wallet.classicAddress,
27
- destination_account: this.destination,
28
- ledger_index: "validated",
29
- });
30
- const channels = resp.result.channels || [];
31
- if (channels.length === 0) {
32
- throw new ChannelNotFound(
33
- `No open payment channel from ${this.wallet.classicAddress} to ${this.destination}`,
34
- );
35
- }
36
- return channels[0];
37
- }
1
+ const { DhaliXrplChannelManager } = require("./DhaliXrplChannelManager");
2
+ const { DhaliEthChannelManager } = require("./DhaliEthChannelManager");
38
3
 
4
+ const DhaliChannelManager = {
39
5
  /**
40
- * Create or fund a payment channel.
41
- * @param {number} amountDrops
42
- * @returns {Promise<object>}
6
+ * @param {import("xrpl").Wallet} wallet
7
+ * @param {import("xrpl").Client} client
8
+ * @param {string} protocol
9
+ * @param {import("./Currency")} currency
10
+ * @param {typeof fetch} [httpClient] - Injected HTTP client
11
+ * @param {object} [publicConfig]
12
+ * @returns {DhaliXrplChannelManager}
43
13
  */
44
- async deposit(amountDrops) {
45
- await this.ready;
46
- let tx;
47
- try {
48
- const ch = await this._findChannel();
49
- tx = {
50
- TransactionType: "PaymentChannelFund",
51
- Account: this.wallet.classicAddress,
52
- Channel: ch.channel_id,
53
- Amount: amountDrops.toString(),
54
- };
55
- } catch (err) {
56
- if (!(err instanceof ChannelNotFound)) throw err;
57
- tx = {
58
- TransactionType: "PaymentChannelCreate",
59
- Account: this.wallet.classicAddress,
60
- Destination: this.destination,
61
- Amount: amountDrops.toString(),
62
- SettleDelay: 86400 * 14,
63
- PublicKey: this.wallet.publicKey,
64
- };
65
- }
66
- // autofill sequence, fee, etc.
67
- const prepared = await this.client.autofill(tx);
68
- // sign
69
- const signed = this.wallet.sign(prepared);
70
- const txBlob = signed.tx_blob || signed.signedTransaction;
71
- // submit & wait
72
- const result = await this.client.submitAndWait(txBlob);
73
- return result.result;
74
- }
14
+ xrpl: (wallet, client, protocol, currency, httpClient, publicConfig) => {
15
+ return new DhaliXrplChannelManager(wallet, client, protocol, currency, httpClient, publicConfig);
16
+ },
75
17
 
76
18
  /**
77
- * Generate a base64-encoded payment claim.
78
- * @param {number=} amountDrops
79
- * @returns {Promise<string>}
19
+ * @param {import("ethers").Signer} signer
20
+ * @param {import("ethers").Provider} provider
21
+ * @param {string} protocol
22
+ * @param {import("./Currency")} currency
23
+ * @param {typeof fetch} [httpClient] - Injected HTTP client
24
+ * @param {object} [publicConfig]
25
+ * @returns {DhaliEthChannelManager}
80
26
  */
81
- async getAuthToken(amountDrops) {
82
- await this.ready;
83
- const ch = await this._findChannel();
84
- const total = BigInt(ch.amount);
85
- const allowed = amountDrops != null ? BigInt(amountDrops) : total;
86
- if (allowed > total) {
87
- throw new Error(
88
- `Requested auth ${allowed} exceeds channel capacity ${total}`,
89
- );
90
- }
91
- const claimHex = buildPaychanAuthHexStringToBeSigned(
92
- ch.channel_id,
93
- allowed.toString(),
27
+ evm: (signer, provider, protocol, currency, httpClient, publicConfig) => {
28
+ return new DhaliEthChannelManager(
29
+ signer,
30
+ provider,
31
+ protocol,
32
+ currency,
33
+ httpClient,
34
+ publicConfig
94
35
  );
95
- const signature = signClaim(claimHex, this.wallet.privateKey);
96
- const claim = {
97
- version: "2",
98
- account: this.wallet.classicAddress,
99
- protocol: this.protocol,
100
- currency: { code: "XRP", scale: 6 },
101
- destination_account: this.destination,
102
- authorized_to_claim: allowed.toString(),
103
- channel_id: ch.channel_id,
104
- signature,
105
- };
106
- return Buffer.from(JSON.stringify(claim)).toString("base64");
107
36
  }
108
- }
37
+ };
109
38
 
110
- module.exports = { DhaliChannelManager, ChannelNotFound };
39
+ module.exports = { DhaliChannelManager };