dhali-js 3.0.3 → 3.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.
@@ -25,8 +25,11 @@ jobs:
25
25
  - name: Install dependencies
26
26
  run: npm ci
27
27
 
28
- - name: Run tests
29
- run: npm test
28
+ - name: Run unit tests
29
+ run: npm test tests/DhaliChannelManager.test.js tests/DhaliEthChannelManager.test.js tests/createSignedClaim.test.js tests/utils.test.js
30
+
31
+ - name: Run integration tests
32
+ run: npm test tests/integration.test.js
30
33
 
31
34
  - name: Run lint
32
35
  run: npx tsc src/dhali/*.js --allowJs --checkJs --noEmit --moduleResolution node --module commonjs --target esnext --skipLibCheck
package/README.md CHANGED
@@ -148,6 +148,81 @@ async function main() {
148
148
  main();
149
149
  ```
150
150
 
151
+
152
+ ---
153
+
154
+ ## Asset Management (for Providers)
155
+
156
+ If you have an API you want to monetize on Dhali, you can use the `DhaliAssetManager` to create and update your asset on the network.
157
+
158
+ ### 1. Create an Asset
159
+
160
+ This generates an **Asset ID (UUID)**.
161
+
162
+ #### XRPL Setup
163
+ ```js
164
+ const { Wallet } = require('xrpl');
165
+ const wallet = Wallet.fromSeed("s..."); // Your XRPL seed
166
+ ```
167
+
168
+ #### EVM Setup
169
+ ```js
170
+ const { createWalletClient, http } = require('viem');
171
+ const { privateKeyToAccount } = require('viem/accounts');
172
+ const { sepolia } = require('viem/chains');
173
+
174
+ const walletClient = createWalletClient({
175
+ account: privateKeyToAccount("0x..."),
176
+ chain: sepolia,
177
+ transport: http()
178
+ });
179
+ ```
180
+
181
+ #### Initialization & Creation
182
+ ```js
183
+ const { DhaliAssetManager, WalletDescriptor, Currency } = require('dhali-js');
184
+
185
+ async function main() {
186
+ // For XRPL
187
+ const manager = DhaliAssetManager.xrpl(wallet);
188
+ const walletDescriptor = new WalletDescriptor(wallet.classicAddress, "XRPL.TESTNET");
189
+
190
+ // OR For EVM
191
+ // const manager = DhaliAssetManager.evm(walletClient);
192
+ // const walletDescriptor = new WalletDescriptor(walletClient.account.address, "SEPOLIA");
193
+
194
+ const currency = new Currency("XRPL.TESTNET", "XRP", 6);
195
+
196
+ // Create the asset
197
+ const result = await manager.createAsset(walletDescriptor, currency);
198
+ console.log("Your new Asset ID:", result.uuid);
199
+ }
200
+ ```
201
+
202
+ Once created, your asset is represented by an **off-chain facilitator address**:
203
+ `https://x402.api.dhali.io/<uuid>`
204
+
205
+ This facilitator is used for protocol-level concerns like verification and settlement, while your actual service requests are sent to your **Resource Server**.
206
+
207
+ ### 2. Update an Asset
208
+
209
+ You can update your asset's metadata (name, rates, etc.) at any time.
210
+
211
+ ```js
212
+ const { AssetUpdates } = require('dhali-js');
213
+
214
+ async function main() {
215
+ const updates = new AssetUpdates({
216
+ name: "My Optimized AI API",
217
+ earning_rate: 100, // 100 drops per request
218
+ earning_type: "per_request" // or "per_second"
219
+ });
220
+
221
+ const result = await manager.updateAsset(assetId, walletDescriptor, updates);
222
+ console.log("Asset updated successfully");
223
+ }
224
+ ```
225
+
151
226
  ---
152
227
 
153
228
  ## API Reference
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dhali-js",
3
- "version": "3.0.3",
3
+ "version": "3.1.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",
@@ -18,6 +18,7 @@
18
18
  "dependencies": {
19
19
  "ripple-keypairs": "^2.0.0",
20
20
  "viem": "^2.23.5",
21
+ "ws": "^8.18.0",
21
22
  "xrpl": "^4.0.0"
22
23
  },
23
24
  "devDependencies": {
@@ -0,0 +1,67 @@
1
+ class AssetUpdates {
2
+ /**
3
+ * @param {Object} options
4
+ * @param {string} [options.name]
5
+ * @param {number} [options.earningRate]
6
+ * @param {string} [options.earningType]
7
+ * @param {string} [options.url]
8
+ * @param {Object} [options.headers]
9
+ * @param {string} [options.docs]
10
+ * @param {number} [options.maxSurcharge]
11
+ * @param {number} [options.assetPricingRate]
12
+ * @param {number} [options.assetPricingMaxSurcharge]
13
+ * @param {Object} [options.assetPricingCurrency]
14
+ */
15
+ constructor({
16
+ name,
17
+ earningRate,
18
+ earningType,
19
+ url,
20
+ headers,
21
+ docs,
22
+ maxSurcharge,
23
+ assetPricingRate,
24
+ assetPricingMaxSurcharge,
25
+ assetPricingCurrency
26
+ } = {}) {
27
+ this.name = name;
28
+ this.earningRate = earningRate;
29
+ this.earningType = earningType;
30
+ this.url = url;
31
+ this.headers = headers;
32
+ this.docs = docs;
33
+ this.maxSurcharge = maxSurcharge;
34
+ this.assetPricingRate = assetPricingRate;
35
+ this.assetPricingMaxSurcharge = assetPricingMaxSurcharge;
36
+ this.assetPricingCurrency = assetPricingCurrency;
37
+ }
38
+
39
+ /**
40
+ * Converts the updates to the format expected by api-admin-gateway
41
+ * @returns {Object}
42
+ */
43
+ toGatewayFormat() {
44
+ const updates = {};
45
+ if (this.name !== undefined) updates["name"] = this.name;
46
+ if (this.earningRate !== undefined) updates["asset_earning_rate"] = this.earningRate;
47
+ if (this.earningType !== undefined) updates["asset_earning_type"] = this.earningType;
48
+ if (this.docs !== undefined) updates["docs"] = this.docs;
49
+ if (this.maxSurcharge !== undefined) updates["asset_earning_max_surcharge"] = this.maxSurcharge;
50
+ if (this.assetPricingRate !== undefined) updates["asset_pricing_rate"] = this.assetPricingRate;
51
+ if (this.assetPricingMaxSurcharge !== undefined) updates["asset_pricing_max_surcharge"] = this.assetPricingMaxSurcharge;
52
+ if (this.assetPricingCurrency !== undefined) updates["asset_pricing_currency"] = this.assetPricingCurrency;
53
+
54
+ // Credentials/URL and headers are special
55
+ if (this.url !== undefined || this.headers !== undefined) {
56
+ updates["api_credentials"] = {};
57
+ if (this.url !== undefined) updates["api_credentials"]["url"] = this.url;
58
+ if (this.headers !== undefined) {
59
+ Object.assign(updates["api_credentials"], this.headers);
60
+ }
61
+ }
62
+
63
+ return updates;
64
+ }
65
+ }
66
+
67
+ module.exports = { AssetUpdates };
@@ -0,0 +1,182 @@
1
+ const { WebSocket } = require('ws');
2
+ const Currency = require('./Currency');
3
+ const { AssetUpdates } = require('./AssetUpdates');
4
+ const { WalletDescriptor } = require('./WalletDescriptor');
5
+ const { fetchPublicConfig } = require('./configUtils');
6
+
7
+ class BaseAssetManager {
8
+ /**
9
+ * @param {any} wallet
10
+ * @param {string} [baseUrl]
11
+ */
12
+ constructor(wallet, baseUrl) {
13
+ this.baseUrl = baseUrl ? baseUrl.replace(/^http/, 'ws') : undefined;
14
+ this.wallet = wallet;
15
+ }
16
+
17
+ async _resolveBaseUrl() {
18
+ if (this.baseUrl) return;
19
+ const config = await fetchPublicConfig();
20
+ const rootUrl = config.ROOT_API_ADMIN_URL;
21
+ if (!rootUrl) {
22
+ throw new Error("ROOT_API_ADMIN_URL not found in public config");
23
+ }
24
+ this.baseUrl = rootUrl.replace(/^http/, 'ws');
25
+ }
26
+
27
+ /**
28
+ * Abstract method to handle protocol-specific signing
29
+ * @protected
30
+ * @returns {Promise<any>}
31
+ */
32
+ async _performSigning(typedData, walletDescriptor) {
33
+ throw new Error("_performSigning must be implemented by subclass");
34
+ }
35
+
36
+ async _handleAuth(ws, message, walletDescriptor) {
37
+ if (message.schema === 'api_admin_gateway_message_to_be_signed') {
38
+ const { message: typedData } = message;
39
+ const authResponse = await this._performSigning(typedData, walletDescriptor);
40
+ ws.send(JSON.stringify(authResponse));
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+
46
+ /**
47
+ * @param {WalletDescriptor} walletDescriptor
48
+ * @param {Currency} currency
49
+ */
50
+ async createAsset(walletDescriptor, currency) {
51
+ await this._resolveBaseUrl();
52
+ if (!(walletDescriptor instanceof WalletDescriptor)) {
53
+ throw new Error('walletDescriptor must be an instance of WalletDescriptor');
54
+ }
55
+ if (!(currency instanceof Currency)) {
56
+ throw new Error('currency must be an instance of Currency');
57
+ }
58
+
59
+ return new Promise((resolve, reject) => {
60
+ const ws = new WebSocket(`${this.baseUrl}/create`);
61
+
62
+ ws.on('open', () => {
63
+ ws.send(JSON.stringify({
64
+ owner: walletDescriptor.toJson(),
65
+ currency: {
66
+ code: currency.code,
67
+ scale: currency.scale,
68
+ issuer: currency.tokenAddress
69
+ }
70
+ }));
71
+ });
72
+
73
+ ws.on('message', async (data) => {
74
+ const message = JSON.parse(data.toString());
75
+
76
+ try {
77
+ if (await this._handleAuth(ws, message, walletDescriptor)) {
78
+ return;
79
+ }
80
+
81
+ if (message.schema === 'api_admin_gateway_request_wallet_json') {
82
+ ws.send(JSON.stringify({
83
+ schema: 'api_admin_gateway_wallet_json_response',
84
+ wallet: walletDescriptor.toJson()
85
+ }));
86
+ } else if (message.schema === 'api_admin_gateway_create_successful') {
87
+ resolve(message);
88
+ ws.close();
89
+ } else if (message.qr_code_url) {
90
+ console.log('Scan this QR code to authenticate:', message.qr_code_url);
91
+ } else if (message.error) {
92
+ reject(new Error(message.error));
93
+ ws.close();
94
+ }
95
+ } catch (err) {
96
+ reject(err);
97
+ ws.close();
98
+ }
99
+ });
100
+
101
+ ws.on('error', (error) => {
102
+ reject(error);
103
+ });
104
+
105
+ ws.on('close', (code, reason) => {
106
+ if (code !== 1000 && code !== 1005) {
107
+ reject(new Error(`WebSocket closed with code ${code}: ${reason}`));
108
+ }
109
+ });
110
+ });
111
+ }
112
+
113
+ /**
114
+ * @param {string} dhaliId
115
+ * @param {WalletDescriptor} walletDescriptor
116
+ * @param {AssetUpdates} updates
117
+ */
118
+ async updateAsset(dhaliId, walletDescriptor, updates) {
119
+ await this._resolveBaseUrl();
120
+ if (!(walletDescriptor instanceof WalletDescriptor)) {
121
+ throw new Error('walletDescriptor must be an instance of WalletDescriptor');
122
+ }
123
+ if (!(updates instanceof AssetUpdates)) {
124
+ throw new Error('updates must be an instance of AssetUpdates');
125
+ }
126
+
127
+ return new Promise((resolve, reject) => {
128
+ const ws = new WebSocket(`${this.baseUrl}/${dhaliId}/update`);
129
+
130
+ ws.on('message', async (data) => {
131
+ const message = JSON.parse(data.toString());
132
+
133
+ try {
134
+ if (await this._handleAuth(ws, message, walletDescriptor)) {
135
+ return;
136
+ }
137
+
138
+ if (message.schema === 'api_admin_gateway_request_wallet_json') {
139
+ ws.send(JSON.stringify({
140
+ schema: 'api_admin_gateway_wallet_json_response',
141
+ wallet: walletDescriptor.toJson()
142
+ }));
143
+ } else if (message.schema === 'api_admin_gateway_authentication_successful') {
144
+ ws.send(JSON.stringify({
145
+ schema: 'api_admin_gateway_prefill_request',
146
+ schema_version: '1.0'
147
+ }));
148
+ } else if (message.schema === 'api_admin_gateway_prefill_response') {
149
+ ws.send(JSON.stringify({
150
+ schema: 'api_admin_gateway_update_request',
151
+ schema_version: '1.0',
152
+ updates: updates.toGatewayFormat()
153
+ }));
154
+ } else if (message.schema === 'api_admin_gateway_update_response') {
155
+ resolve(message);
156
+ ws.close();
157
+ } else if (message.qr_code_url) {
158
+ console.log('Scan this QR code to authenticate:', message.qr_code_url);
159
+ } else if (message.error || message.status === 'failed') {
160
+ reject(new Error(message.error || 'Update failed'));
161
+ ws.close();
162
+ }
163
+ } catch (err) {
164
+ reject(err);
165
+ ws.close();
166
+ }
167
+ });
168
+
169
+ ws.on('error', (error) => {
170
+ reject(error);
171
+ });
172
+
173
+ ws.on('close', (code, reason) => {
174
+ if (code !== 1000 && code !== 1005) {
175
+ reject(new Error(`WebSocket closed with code ${code}: ${reason}`));
176
+ }
177
+ });
178
+ });
179
+ }
180
+ }
181
+
182
+ module.exports = { BaseAssetManager };
@@ -0,0 +1,25 @@
1
+ const { DhaliXrplAssetManager } = require('./DhaliXrplAssetManager');
2
+ const { DhaliEthAssetManager } = require('./DhaliEthAssetManager');
3
+
4
+ /**
5
+ * Factory for creating asset managers.
6
+ */
7
+ const DhaliAssetManager = {
8
+ /**
9
+ * @param {import("xrpl").Wallet} wallet
10
+ * @returns {DhaliXrplAssetManager}
11
+ */
12
+ xrpl: (wallet) => {
13
+ return new DhaliXrplAssetManager(wallet);
14
+ },
15
+
16
+ /**
17
+ * @param {import("viem").WalletClient} walletClient
18
+ * @returns {DhaliEthAssetManager}
19
+ */
20
+ evm: (walletClient) => {
21
+ return new DhaliEthAssetManager(walletClient);
22
+ }
23
+ };
24
+
25
+ module.exports = { DhaliAssetManager };
@@ -0,0 +1,35 @@
1
+ const { BaseAssetManager } = require('./BaseAssetManager');
2
+
3
+ /**
4
+ * DhaliAssetManager for EVM protocol.
5
+ */
6
+ class DhaliEthAssetManager extends BaseAssetManager {
7
+ /**
8
+ * @param {import("viem").WalletClient} walletClient
9
+ * @param {string} [baseUrl]
10
+ */
11
+ constructor(walletClient, baseUrl) {
12
+ super(walletClient, baseUrl);
13
+ }
14
+
15
+ /**
16
+ * @protected
17
+ */
18
+ async _performSigning(typedData, walletDescriptor) {
19
+ const [account] = await this.wallet.getAddresses();
20
+ const signature = await this.wallet.signTypedData({
21
+ account: this.wallet.account || account,
22
+ domain: typedData.domain,
23
+ types: typedData.types,
24
+ primaryType: typedData.primaryType,
25
+ message: typedData.message
26
+ });
27
+
28
+ return {
29
+ schema: 'api_admin_gateway_signed_message_response',
30
+ signature: signature
31
+ };
32
+ }
33
+ }
34
+
35
+ module.exports = { DhaliEthAssetManager };
@@ -49,7 +49,7 @@ class DhaliEthChannelManager {
49
49
 
50
50
  if (!this.destinationAddress) {
51
51
  try {
52
- this.destinationAddress = this.public_config.DHALI_PUBLIC_ADDRESSES[this.currency.network][this.currency.code].wallet_id.toLowerCase();
52
+ this.destinationAddress = this.public_config.DHALI_PUBLIC_ADDRESSES[this.currency.network][this.currency.code].wallet_id;
53
53
  } catch (e) {
54
54
  throw new Error("Destination address not found in public_config for this protocol/currency: " + e.message);
55
55
  }
@@ -332,7 +332,7 @@ class DhaliEthChannelManager {
332
332
  scale: this.currency.scale,
333
333
  issuer: this.currency.tokenAddress || null
334
334
  },
335
- destination_account: this.destinationAddress.toLowerCase(),
335
+ destination_account: this.destinationAddress,
336
336
  authorized_to_claim: allowed,
337
337
  channel_id: channelId,
338
338
  signature: signature
@@ -0,0 +1,32 @@
1
+ const { BaseAssetManager } = require('./BaseAssetManager');
2
+ const { sign: signClaim } = require("ripple-keypairs");
3
+
4
+ /**
5
+ * DhaliAssetManager for XRPL protocol.
6
+ */
7
+ class DhaliXrplAssetManager extends BaseAssetManager {
8
+ /**
9
+ * @param {import("xrpl").Wallet} wallet
10
+ * @param {string} [baseUrl]
11
+ */
12
+ constructor(wallet, baseUrl) {
13
+ super(wallet, baseUrl);
14
+ }
15
+
16
+ /**
17
+ * @protected
18
+ */
19
+ async _performSigning(typedData, walletDescriptor) {
20
+ // The backend expects the message as a JSON string for XRPL
21
+ const messageToSign = JSON.stringify(typedData);
22
+ const signature = signClaim(Buffer.from(messageToSign).toString('hex'), this.wallet.privateKey);
23
+
24
+ return {
25
+ schema: 'api_admin_gateway_signed_message_response',
26
+ signature: signature.toUpperCase(),
27
+ public_key: this.wallet.publicKey
28
+ };
29
+ }
30
+ }
31
+
32
+ module.exports = { DhaliXrplAssetManager };
@@ -0,0 +1,27 @@
1
+ class WalletDescriptor {
2
+ /**
3
+ * @param {string} address - The classic address of the wallet
4
+ * @param {string} protocol - The network protocol (e.g., 'XRPL.TESTNET', 'ETHEREUM')
5
+ * @param {string} [type='Dhali-js'] - The type of wallet (defaults to 'Dhali-js')
6
+ */
7
+ constructor(address, protocol, type = 'Dhali-js') {
8
+ this.address = address;
9
+ this.protocol = protocol;
10
+ this.type = type;
11
+ }
12
+
13
+ /**
14
+ * Converts to JSON format expected by Dhali backend
15
+ * @returns {Object}
16
+ */
17
+ toJson() {
18
+ return {
19
+ address: this.address,
20
+ wallet_id: this.address, // Backward compatibility or specific gateway requirement
21
+ type: this.type,
22
+ protocol: this.protocol
23
+ };
24
+ }
25
+ }
26
+
27
+ module.exports = { WalletDescriptor };
package/src/index.js CHANGED
@@ -1,9 +1,15 @@
1
1
  const { DhaliChannelManager } = require("./dhali/DhaliChannelManager");
2
- const { DhaliXrplChannelManager, ChannelNotFound } = require("./dhali/DhaliXrplChannelManager");
2
+ const { DhaliXrplChannelManager } = require("./dhali/DhaliXrplChannelManager");
3
3
  const { DhaliEthChannelManager } = require("./dhali/DhaliEthChannelManager");
4
+ const { DhaliAssetManager } = require("./dhali/DhaliAssetManager");
5
+ const { BaseAssetManager } = require("./dhali/BaseAssetManager");
6
+ const { DhaliXrplAssetManager } = require("./dhali/DhaliXrplAssetManager");
7
+ const { DhaliEthAssetManager } = require("./dhali/DhaliEthAssetManager");
8
+ const { WalletDescriptor } = require("./dhali/WalletDescriptor");
9
+ const { AssetUpdates } = require("./dhali/AssetUpdates");
4
10
  const Currency = require("./dhali/Currency");
5
- const { getAvailableDhaliCurrencies } = require("./dhali/configUtils");
6
- const { wrapAsX402PaymentPayload } = require("./dhali/utils");
11
+ const { fetchPublicConfig, retrieveChannelIdFromFirestoreRest, notifyAdminGateway, getAvailableDhaliCurrencies } = require("./dhali/configUtils");
12
+ const { wrapAsX402PaymentPayload, ChannelNotFound } = require("./dhali/utils");
7
13
 
8
14
  module.exports = {
9
15
  DhaliChannelManager,
@@ -12,5 +18,14 @@ module.exports = {
12
18
  ChannelNotFound,
13
19
  Currency,
14
20
  getAvailableDhaliCurrencies,
15
- wrapAsX402PaymentPayload
21
+ DhaliAssetManager,
22
+ BaseAssetManager,
23
+ DhaliXrplAssetManager,
24
+ DhaliEthAssetManager,
25
+ WalletDescriptor,
26
+ AssetUpdates,
27
+ fetchPublicConfig,
28
+ retrieveChannelIdFromFirestoreRest,
29
+ notifyAdminGateway,
30
+ wrapAsX402PaymentPayload,
16
31
  };
@@ -294,4 +294,32 @@ describe("DhaliEthChannelManager", () => {
294
294
  mockHttp
295
295
  );
296
296
  });
297
+
298
+ test("getAuthToken preserves destination_account casing", async () => {
299
+ const mixedCaseDestination = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F";
300
+ const mixedCaseConfig = {
301
+ DHALI_PUBLIC_ADDRESSES: {
302
+ ETHEREUM: {
303
+ ETH: { wallet_id: mixedCaseDestination }
304
+ }
305
+ },
306
+ CONTRACTS: {
307
+ ETHEREUM: { contract_address: "0x0000000000000000000000000000000000000003" }
308
+ }
309
+ };
310
+
311
+ const localManager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, null, mixedCaseConfig);
312
+
313
+ // Mock required dependencies for getAuthToken
314
+ configUtils.retrieveChannelIdFromFirestoreRest.mockResolvedValue("0xChannelId");
315
+ const amountHex = BigInt(1000).toString(16).padStart(64, '0');
316
+ mockPublicClient.call = jest.fn().mockResolvedValue({ data: "0x" + "0".repeat(64 * 4) + amountHex });
317
+ mockWalletClient.signTypedData.mockResolvedValue("0xSignature");
318
+
319
+ const token = await localManager.getAuthToken(100);
320
+ const decoded = JSON.parse(Buffer.from(token, "base64").toString("utf8"));
321
+
322
+ expect(decoded.destination_account).toBe(mixedCaseDestination);
323
+ expect(decoded.destination_account).not.toBe(mixedCaseDestination.toLowerCase());
324
+ });
297
325
  });
@@ -0,0 +1,78 @@
1
+ const { Wallet } = require('xrpl');
2
+ const { DhaliAssetManager } = require('../src/dhali/DhaliAssetManager');
3
+ const { WalletDescriptor } = require('../src/dhali/WalletDescriptor');
4
+ const Currency = require('../src/dhali/Currency');
5
+ const { AssetUpdates } = require('../src/dhali/AssetUpdates');
6
+
7
+ describe('Dhali-js Integration Test', () => {
8
+ let wallet;
9
+ let manager;
10
+
11
+ beforeAll(() => {
12
+ wallet = Wallet.generate();
13
+ manager = DhaliAssetManager.xrpl(wallet);
14
+ });
15
+
16
+ test('should create and update an asset', async () => {
17
+ const walletDescriptor = new WalletDescriptor(wallet.address, "XRPL.TESTNET");
18
+ const currency = new Currency("XRPL.TESTNET", "XRP", 6);
19
+
20
+ // 1. Create Asset
21
+ console.log('Creating asset...');
22
+ const createResult = await manager.createAsset(walletDescriptor, currency);
23
+ expect(createResult.schema).toBe('api_admin_gateway_create_successful');
24
+ expect(createResult.uuid).toBeDefined();
25
+ const assetId = createResult.uuid;
26
+ console.log('Asset created with UUID:', assetId);
27
+
28
+ // 2. Update Asset
29
+ console.log('Updating asset...');
30
+ const updates = new AssetUpdates({
31
+ name: "Integration Test Asset",
32
+ earning_rate: 100,
33
+ earning_type: "per_request"
34
+ });
35
+
36
+ const updateResult = await manager.updateAsset(assetId, walletDescriptor, updates);
37
+ expect(updateResult.schema).toBe('api_admin_gateway_update_response');
38
+ console.log('Asset updated successfully');
39
+ }, 30000); // Increased timeout for live API calls
40
+
41
+ test('should create and update an EVM asset', async () => {
42
+ const { createWalletClient, http } = require('viem');
43
+ const { generatePrivateKey, privateKeyToAccount } = require('viem/accounts');
44
+ const { sepolia } = require('viem/chains');
45
+
46
+ const privateKey = generatePrivateKey();
47
+ const account = privateKeyToAccount(privateKey);
48
+ const walletClient = createWalletClient({
49
+ account,
50
+ chain: sepolia,
51
+ transport: http()
52
+ });
53
+
54
+ const evmManager = DhaliAssetManager.evm(walletClient);
55
+ const walletDescriptor = new WalletDescriptor(account.address, "SEPOLIA");
56
+ const currency = new Currency("SEPOLIA", "ETH", 18);
57
+
58
+ // 1. Create Asset
59
+ console.log('Creating EVM asset...');
60
+ const createResult = await evmManager.createAsset(walletDescriptor, currency);
61
+ expect(createResult.schema).toBe('api_admin_gateway_create_successful');
62
+ expect(createResult.uuid).toBeDefined();
63
+ const assetId = createResult.uuid;
64
+ console.log('EVM Asset created with UUID:', assetId);
65
+
66
+ // 2. Update Asset
67
+ console.log('Updating EVM asset...');
68
+ const updates = new AssetUpdates({
69
+ name: "Integration Test Asset EVM",
70
+ earning_rate: 0.001,
71
+ earning_type: "per_request"
72
+ });
73
+
74
+ const updateResult = await evmManager.updateAsset(assetId, walletDescriptor, updates);
75
+ expect(updateResult.schema).toBe('api_admin_gateway_update_response');
76
+ console.log('EVM Asset updated successfully');
77
+ }, 30000);
78
+ });