dhali-js 2.1.0 → 3.0.3

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.
@@ -1,331 +1,345 @@
1
1
  const { getEthereumClaimTypedData } = require("./createSignedClaim");
2
- const { ethers } = require("ethers");
2
+ const {
3
+ keccak256,
4
+ encodeAbiParameters,
5
+ parseAbiParameters,
6
+ toHex
7
+ } = require("viem");
8
+ const crypto = require("crypto");
3
9
 
4
10
  const { fetchPublicConfig, notifyAdminGateway, retrieveChannelIdFromFirestoreRest } = require("./configUtils");
11
+ const { ChannelNotFound } = require("./utils");
5
12
 
6
13
  class DhaliEthChannelManager {
7
- /**
8
- * @param {ethers.Signer} signer
9
- * @param {import("ethers").Provider} rpc_client
10
- * @param {string} protocol
11
- * @param {import("./Currency")} currency
12
- * @param {typeof fetch} [httpClient] - Injected HTTP client
13
- * @param {object} [public_config] - Dhali public configuration
14
- */
15
- constructor(signer, rpc_client, protocol, currency, httpClient = fetch, public_config) {
16
- this.signer = signer;
17
- this.rpc_client = rpc_client;
18
- this.protocol = protocol;
19
- this.currency = currency;
20
- this.httpClient = httpClient || fetch;
21
- this.public_config = public_config;
22
- this.chainId = this._getChainIdFromProtocol(protocol);
23
- this.destinationAddress = undefined;
24
- this.contractAddress = undefined;
14
+ /**
15
+ * @param {import("viem").WalletClient} walletClient
16
+ * @param {import("viem").PublicClient} publicClient
17
+ * @param {import("./Currency")} currency
18
+ * @param {typeof fetch} [httpClient] - Injected HTTP client
19
+ * @param {object} [public_config] - Dhali public configuration
20
+ */
21
+ constructor(walletClient, publicClient, currency, httpClient = fetch, public_config) {
22
+ this.walletClient = walletClient;
23
+ this.publicClient = publicClient;
24
+ this.currency = currency;
25
+ this.httpClient = httpClient || fetch;
26
+ this.public_config = public_config;
27
+ this.chainId = this._getChainIdFromProtocol(this.currency.network);
28
+ this.destinationAddress = undefined;
29
+ this.contractAddress = undefined;
30
+ }
31
+
32
+ _getChainIdFromProtocol(protocol) {
33
+ switch (protocol.toUpperCase()) {
34
+ case "ETHEREUM": return 1;
35
+ case "SEPOLIA": return 11155111;
36
+ case "HOLESKY": return 17000;
37
+ case "HARDHAT": return 31337;
38
+ default: throw new Error(`Unsupported protocol: ${protocol}`);
25
39
  }
40
+ }
26
41
 
27
- _getChainIdFromProtocol(protocol) {
28
- switch (protocol) {
29
- case "ETHEREUM": return 1;
30
- case "SEPOLIA": return 11155111;
31
- case "HOLESKY": return 17000;
32
- case "LOCALHOST": return 31337;
33
- default: throw new Error(`Unsupported protocol: ${protocol}`);
34
- }
35
- }
36
42
 
37
- _getProtocolName() {
38
- return this.protocol;
39
- }
43
+ async _resolveAddresses() {
44
+ if (this.destinationAddress && this.contractAddress) return;
40
45
 
41
- async _resolveAddresses() {
42
- if (this.destinationAddress && this.contractAddress) return;
43
-
44
- if (!this.public_config) {
45
- this.public_config = await fetchPublicConfig(this.httpClient);
46
- }
47
-
48
- if (!this.destinationAddress) {
49
- try {
50
- this.destinationAddress = this.public_config.DHALI_PUBLIC_ADDRESSES[this.protocol][this.currency.code].wallet_id;
51
- } catch (e) {
52
- throw new Error("Destination address not found in public_config for this protocol/currency: " + e.message);
53
- }
54
- }
55
-
56
- if (!this.contractAddress) {
57
- try {
58
- // @ts-ignore
59
- this.contractAddress = this.public_config.CONTRACTS[this.protocol].contract_address;
60
- } catch (e) {
61
- throw new Error("Contract address not found in public_config for this protocol: " + e.message);
62
- }
63
- }
64
-
65
- if (!this.contractAddress) {
66
- throw new Error("Contract address must be provided or resolved for this chainId");
67
- }
46
+ if (!this.public_config) {
47
+ this.public_config = await fetchPublicConfig(this.httpClient);
68
48
  }
69
49
 
70
- /**
71
- * Queries Firestore for an existing open channel.
72
- * Path: public_claim_info/<protocol>/<currency_identifier>
73
- * Filter: account == my_address, closed != true
74
- */
75
- async _retrieveChannelIdFromFirestore() {
76
- const address = (await this.signer.getAddress()).toLowerCase();
77
- return await retrieveChannelIdFromFirestoreRest(
78
- this.protocol,
79
- this.currency,
80
- address,
81
- this.httpClient
82
- );
50
+ if (!this.destinationAddress) {
51
+ try {
52
+ this.destinationAddress = this.public_config.DHALI_PUBLIC_ADDRESSES[this.currency.network][this.currency.code].wallet_id.toLowerCase();
53
+ } catch (e) {
54
+ throw new Error("Destination address not found in public_config for this protocol/currency: " + e.message);
55
+ }
83
56
  }
84
57
 
85
- async _retrieveChannelIdFromFirestoreWithPolling(timeoutSeconds = 30) {
86
- const startTime = Date.now();
87
- while (Date.now() - startTime < timeoutSeconds * 1000) {
88
- const channelId = await this._retrieveChannelIdFromFirestore();
89
- if (channelId) return channelId;
90
- await new Promise(resolve => setTimeout(resolve, 2000));
91
- }
92
- return null;
58
+ if (!this.contractAddress) {
59
+ try {
60
+ // @ts-ignore
61
+ this.contractAddress = this.public_config.CONTRACTS[this.currency.network].contract_address;
62
+ } catch (e) {
63
+ throw new Error("Contract address not found in public_config for this protocol: " + e.message);
64
+ }
93
65
  }
94
66
 
95
- async _calculateChannelId(receiver, tokenAddress, nonce) {
96
- // Matches Dhali-wallet: keccak256(abi.encode(sender, receiver, token, nonce))
97
- const sender = await this.signer.getAddress();
98
- return ethers.keccak256(
99
- ethers.AbiCoder.defaultAbiCoder().encode(
100
- ["address", "address", "address", "uint256"],
101
- [sender, receiver, tokenAddress, nonce]
102
- )
103
- );
67
+ if (!this.contractAddress) {
68
+ throw new Error("Contract address must be provided or resolved for this chainId");
104
69
  }
105
-
106
- _encodeAddress(address) {
107
- return address.toLowerCase().replace("0x", "").padStart(64, '0');
70
+ }
71
+
72
+ /**
73
+ * Queries Firestore for an existing open channel.
74
+ * Path: public_claim_info/<protocol>/<currency_identifier>
75
+ * Filter: account == my_address, closed != true
76
+ */
77
+ async _retrieveChannelIdFromFirestore() {
78
+ const [address] = await this.walletClient.getAddresses();
79
+ return await retrieveChannelIdFromFirestoreRest(
80
+ this.currency.network,
81
+ this.currency,
82
+ address.toLowerCase(),
83
+ this.httpClient
84
+ );
85
+ }
86
+
87
+ async _retrieveChannelIdFromFirestoreWithPolling(timeoutSeconds = 30) {
88
+ const startTime = Date.now();
89
+ while (Date.now() - startTime < timeoutSeconds * 1000) {
90
+ const channelId = await this._retrieveChannelIdFromFirestore();
91
+ if (channelId) return channelId;
92
+ await new Promise(resolve => setTimeout(resolve, 2000));
108
93
  }
109
-
110
- _encodeUint(value) {
111
- return BigInt(value).toString(16).padStart(64, '0');
94
+ return null;
95
+ }
96
+
97
+ async _calculateChannelId(receiver, tokenAddress, nonce) {
98
+ // Matches Dhali-wallet: keccak256(abi.encode(sender, receiver, token, nonce))
99
+ const [sender] = await this.walletClient.getAddresses();
100
+ return keccak256(
101
+ encodeAbiParameters(
102
+ parseAbiParameters("address, address, address, uint256"),
103
+ [sender, receiver, tokenAddress, nonce]
104
+ )
105
+ );
106
+ }
107
+
108
+ _encodeAddress(address) {
109
+ return address.toLowerCase().replace("0x", "").padStart(64, '0');
110
+ }
111
+
112
+ _encodeUint(value) {
113
+ return BigInt(value).toString(16).padStart(64, '0');
114
+ }
115
+
116
+ _encodeBool(value) {
117
+ return value ? "1".padStart(64, '0') : "0".padStart(64, '0');
118
+ }
119
+
120
+ _encodeBytes32(value) {
121
+ return value.replace("0x", "").padStart(64, '0');
122
+ }
123
+
124
+ async _buildTx(to, data, value) {
125
+ const accountString = (await this.walletClient.getAddresses())[0];
126
+ const account = this.walletClient.account || accountString;
127
+ const gasPrice = await this.publicClient.getGasPrice();
128
+ // Add 10% buffer to gas price
129
+ const gasPriceWithBuffer = (gasPrice * BigInt(110)) / BigInt(100);
130
+
131
+ /** @type {any} */
132
+ const txParams = {
133
+ account: account,
134
+ to: to,
135
+ value: value,
136
+ data: data,
137
+ gasPrice: gasPriceWithBuffer,
138
+ nonce: await this.publicClient.getTransactionCount({ address: accountString, blockTag: "pending" }),
139
+ chain: { id: this.chainId }
140
+ };
141
+
142
+ // Estimate gas
143
+ const gasLimit = await this.publicClient.estimateGas(txParams);
144
+ // Add 10% buffer to gas limit
145
+ txParams.gas = (gasLimit * BigInt(110)) / BigInt(100);
146
+
147
+ return txParams;
148
+ }
149
+
150
+ /**
151
+ * Deposits funds into a payment channel.
152
+ * If an open channel exists, funds it.
153
+ * If not, opens a new one.
154
+ * @param {string|number} amount Amount in base units (wei/drops)
155
+ * @returns {Promise<import("viem").TransactionReceipt>}
156
+ */
157
+ async deposit(amount) {
158
+ await this._resolveAddresses();
159
+ const existingChannelId = await this._retrieveChannelIdFromFirestore();
160
+ const tokenAddress = this.currency.tokenAddress || "0x0000000000000000000000000000000000000000";
161
+ const isNative = (tokenAddress === "0x0000000000000000000000000000000000000000");
162
+ const amountBig = BigInt(amount);
163
+
164
+ const OPEN_CHANNEL_SELECTOR = "3cd880a5";
165
+ const DEPOSIT_SELECTOR = "264d06c8";
166
+ const SETTLE_DELAY = 1209600n; // 2 weeks
167
+
168
+ if (existingChannelId) {
169
+ // Deposit
170
+ const calldata = "0x" +
171
+ DEPOSIT_SELECTOR +
172
+ this._encodeBytes32(existingChannelId) +
173
+ this._encodeUint(amountBig) +
174
+ this._encodeBool(true); // renew
175
+
176
+ if (!isNative) {
177
+ await this._approveToken(tokenAddress, this.contractAddress, amountBig);
178
+ }
179
+
180
+ const txParams = await this._buildTx(this.contractAddress, calldata, isNative ? amountBig : 0n);
181
+ const hash = await this.walletClient.sendTransaction(txParams);
182
+ return await this.publicClient.waitForTransactionReceipt({ hash });
183
+
184
+ } else {
185
+ // Open Channel
186
+ const receiver = this.destinationAddress;
187
+ const nonce = this._generateNonce();
188
+ const dummySigner = "0x0000000000000000000000000000000000000000";
189
+
190
+ const calldata = "0x" +
191
+ OPEN_CHANNEL_SELECTOR +
192
+ this._encodeAddress(receiver) +
193
+ this._encodeAddress(tokenAddress) +
194
+ this._encodeUint(amountBig) +
195
+ this._encodeUint(SETTLE_DELAY) +
196
+ this._encodeUint(nonce) +
197
+ this._encodeAddress(dummySigner);
198
+
199
+ if (!isNative) {
200
+ await this._approveToken(tokenAddress, this.contractAddress, amountBig);
201
+ }
202
+
203
+ const txParams = await this._buildTx(this.contractAddress, calldata, isNative ? amountBig : 0n);
204
+ const hash = await this.walletClient.sendTransaction(txParams);
205
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
206
+
207
+ // Calculate channel ID and notify gateway
208
+ const calculatedChannelId = await this._calculateChannelId(receiver, tokenAddress, nonce);
209
+
210
+ let currencyIdentifier = this.currency.code;
211
+ if (this.currency.tokenAddress) {
212
+ currencyIdentifier = `${this.currency.code}.${this.currency.tokenAddress}`;
213
+ }
214
+
215
+ const [address] = await this.walletClient.getAddresses();
216
+ // Proactive notification
217
+ await notifyAdminGateway(
218
+ this.currency.network,
219
+ currencyIdentifier,
220
+ address.toLowerCase(),
221
+ calculatedChannelId,
222
+ this.httpClient
223
+ );
224
+
225
+ // Poll Firestore to match setupBalanceListener behavior
226
+ await this._retrieveChannelIdFromFirestoreWithPolling(30);
227
+
228
+ return receipt;
112
229
  }
113
-
114
- _encodeBool(value) {
115
- return value ? "1".padStart(64, '0') : "0".padStart(64, '0');
230
+ }
231
+
232
+ _generateNonce() {
233
+ const bytes = crypto.randomBytes(32);
234
+ return BigInt(toHex(bytes));
235
+ }
236
+
237
+ async _approveToken(tokenAddress, spender, amount) {
238
+ const APPROVE_SELECTOR = "095ea7b3";
239
+ const calldata = "0x" +
240
+ APPROVE_SELECTOR +
241
+ this._encodeAddress(spender) +
242
+ this._encodeUint(amount);
243
+
244
+ const txParams = await this._buildTx(tokenAddress, calldata, 0n);
245
+ const hash = await this.walletClient.sendTransaction(txParams);
246
+ await this.publicClient.waitForTransactionReceipt({ hash });
247
+ }
248
+ async _getOnChainChannelAmount(channelId) {
249
+ const cleanId = channelId.replace("0x", "").padStart(64, "0");
250
+ const calldata = "0x831c2b82" + cleanId;
251
+
252
+ try {
253
+ const result = await this.publicClient.call({
254
+ to: this.contractAddress,
255
+ data: calldata
256
+ });
257
+
258
+ if (!result || result.data === "0x" || result.data.length < 322) {
259
+ throw new Error("Invalid getChannel response length");
260
+ }
261
+
262
+ // The amount is the 5th 32-byte word (index 4).
263
+ // Result is a hex string "0x...".
264
+ // Word 0: 2 to 66
265
+ // Word 1: 66 to 130
266
+ // Word 2: 130 to 194
267
+ // Word 3: 194 to 258
268
+ // Word 4: 258 to 322
269
+ const amountHex = "0x" + result.data.substring(258, 322);
270
+ return BigInt(amountHex).toString();
271
+ } catch (e) {
272
+ throw new Error(`Failed to retrieve on-chain channel amount: ${e.message}`);
116
273
  }
117
-
118
- _encodeBytes32(value) {
119
- return value.replace("0x", "").padStart(64, '0');
274
+ }
275
+
276
+ /**
277
+ * Generate a base64-encoded payment claim.
278
+ * @param {number|string|null} amount - Defaults to total channel capacity if null
279
+ * @returns {Promise<string>}
280
+ */
281
+ async getAuthToken(amount = null) {
282
+ await this._resolveAddresses();
283
+ // Poll Firestore if not found (setupBalanceListener simulation)
284
+ const channelIdRaw = await this._retrieveChannelIdFromFirestoreWithPolling(10);
285
+ if (!channelIdRaw) {
286
+ throw new ChannelNotFound("No open payment channel found in Firestore. Please deposit first.");
120
287
  }
121
288
 
122
- async _buildTx(to, data, value) {
123
- const feeData = await this.rpc_client.getFeeData();
124
- // Add 10% buffer to gas price
125
- const gasPrice = (feeData.gasPrice * BigInt(110)) / BigInt(100);
126
-
127
- const txParams = {
128
- from: await this.signer.getAddress(),
129
- to: to,
130
- value: value,
131
- data: data,
132
- gasPrice: gasPrice,
133
- // Use pending nonce
134
- nonce: await this.signer.getNonce("pending"),
135
- chainId: this.chainId
136
- };
137
-
138
- // Estimate gas
139
- const gasLimit = await this.signer.estimateGas(txParams);
140
- // Add 10% buffer to gas limit
141
- txParams.gasLimit = (gasLimit * BigInt(110)) / BigInt(100);
142
-
143
- return txParams;
289
+ let channelId = channelIdRaw;
290
+ if (!channelId.startsWith("0x")) {
291
+ channelId = "0x" + channelId;
144
292
  }
145
293
 
146
- /**
147
- * Deposits funds into a payment channel.
148
- * If an open channel exists, funds it.
149
- * If not, opens a new one.
150
- * @param {string|number} amount Amount in base units (wei/drops)
151
- * @returns {Promise<ethers.TransactionReceipt>}
152
- */
153
- async deposit(amount) {
154
- await this._resolveAddresses();
155
- const existingChannelId = await this._retrieveChannelIdFromFirestore();
156
- const tokenAddress = this.currency.tokenAddress || "0x0000000000000000000000000000000000000000";
157
- const isNative = (tokenAddress === "0x0000000000000000000000000000000000000000");
158
- const amountBig = BigInt(amount);
159
-
160
- const OPEN_CHANNEL_SELECTOR = "3cd880a5";
161
- const DEPOSIT_SELECTOR = "264d06c8";
162
- const SETTLE_DELAY = 1209600; // 2 weeks
163
-
164
- if (existingChannelId) {
165
- // Deposit
166
- const calldata = "0x" +
167
- DEPOSIT_SELECTOR +
168
- this._encodeBytes32(existingChannelId) +
169
- this._encodeUint(amountBig) +
170
- this._encodeBool(true); // renew
171
-
172
- if (!isNative) {
173
- await this._approveToken(tokenAddress, this.contractAddress, amountBig);
174
- }
175
-
176
- const txParams = await this._buildTx(this.contractAddress, calldata, isNative ? amountBig : 0);
177
- const tx = await this.signer.sendTransaction(txParams);
178
- return await tx.wait();
179
-
180
- } else {
181
- // Open Channel
182
- const receiver = this.destinationAddress;
183
- const nonce = this._generateNonce();
184
- const dummySigner = "0x0000000000000000000000000000000000000000";
185
-
186
- const calldata = "0x" +
187
- OPEN_CHANNEL_SELECTOR +
188
- this._encodeAddress(receiver) +
189
- this._encodeAddress(tokenAddress) +
190
- this._encodeUint(amountBig) +
191
- this._encodeUint(SETTLE_DELAY) +
192
- this._encodeUint(nonce) +
193
- this._encodeAddress(dummySigner);
194
-
195
- if (!isNative) {
196
- await this._approveToken(tokenAddress, this.contractAddress, amountBig);
197
- }
198
-
199
- const txParams = await this._buildTx(this.contractAddress, calldata, isNative ? amountBig : 0);
200
- const tx = await this.signer.sendTransaction(txParams);
201
- const receipt = await tx.wait();
202
-
203
- // Calculate channel ID and notify gateway
204
- const calculatedChannelId = await this._calculateChannelId(receiver, tokenAddress, nonce);
205
-
206
- let currencyIdentifier = this.currency.code;
207
- if (this.currency.tokenAddress) {
208
- currencyIdentifier = `${this.currency.code}.${this.currency.tokenAddress}`;
209
- }
210
-
211
- // Proactive notification
212
- await notifyAdminGateway(
213
- this.protocol,
214
- currencyIdentifier,
215
- await this.signer.getAddress(),
216
- calculatedChannelId,
217
- this.httpClient
218
- );
219
-
220
- // Poll Firestore to match setupBalanceListener behavior
221
- await this._retrieveChannelIdFromFirestoreWithPolling(30);
222
-
223
- return receipt;
224
- }
225
- }
294
+ const totalAmount = await this._getOnChainChannelAmount(channelId);
295
+ const allowed = amount !== null ? amount.toString() : totalAmount;
226
296
 
227
- _generateNonce() {
228
- const bytes = ethers.randomBytes(32);
229
- return BigInt(ethers.hexlify(bytes));
297
+ // BigInt comparison if needed, but for now simple check if it exceeds
298
+ if (BigInt(allowed) > BigInt(totalAmount)) {
299
+ throw new Error(`Requested auth ${allowed} exceeds channel capacity ${totalAmount}`);
230
300
  }
231
301
 
232
- async _approveToken(tokenAddress, spender, amount) {
233
- const APPROVE_SELECTOR = "095ea7b3";
234
- const calldata = "0x" +
235
- APPROVE_SELECTOR +
236
- this._encodeAddress(spender) +
237
- this._encodeUint(amount);
238
-
239
- const txParams = await this._buildTx(tokenAddress, calldata, 0);
240
- const tx = await this.signer.sendTransaction(txParams);
241
- await tx.wait();
242
- }
243
- async _getOnChainChannelAmount(channelId) {
244
- const cleanId = channelId.replace("0x", "").padStart(64, "0");
245
- const calldata = "0x831c2b82" + cleanId;
246
-
247
- try {
248
- const result = await this.rpc_client.call({
249
- to: this.contractAddress,
250
- data: calldata
251
- });
252
-
253
- if (result === "0x" || result.length < 322) {
254
- throw new Error("Invalid getChannel response length");
255
- }
256
-
257
- // The amount is the 5th 32-byte word (index 4).
258
- // Result is a hex string "0x...".
259
- // Word 0: 2 to 66
260
- // Word 1: 66 to 130
261
- // Word 2: 130 to 194
262
- // Word 3: 194 to 258
263
- // Word 4: 258 to 322
264
- const amountHex = "0x" + result.substring(258, 322);
265
- return BigInt(amountHex).toString();
266
- } catch (e) {
267
- throw new Error(`Failed to retrieve on-chain channel amount: ${e.message}`);
268
- }
302
+ if (!channelId.startsWith("0x")) {
303
+ channelId = "0x" + channelId;
269
304
  }
270
305
 
271
- /**
272
- * Generate a base64-encoded payment claim.
273
- * @param {number|string|null} amount - Defaults to total channel capacity if null
274
- * @returns {Promise<string>}
275
- */
276
- async getAuthToken(amount = null) {
277
- await this._resolveAddresses();
278
- // Poll Firestore if not found (setupBalanceListener simulation)
279
- const channelIdRaw = await this._retrieveChannelIdFromFirestoreWithPolling(10);
280
- if (!channelIdRaw) {
281
- throw new Error("No open payment channel found in Firestore. Please deposit first.");
282
- }
283
-
284
- let channelId = channelIdRaw;
285
- if (!channelId.startsWith("0x")) {
286
- channelId = "0x" + channelId;
287
- }
288
-
289
- const totalAmount = await this._getOnChainChannelAmount(channelId);
290
- const allowed = amount !== null ? amount.toString() : totalAmount;
291
-
292
- // BigInt comparison if needed, but for now simple check if it exceeds
293
- if (BigInt(allowed) > BigInt(totalAmount)) {
294
- throw new Error(`Requested auth ${allowed} exceeds channel capacity ${totalAmount}`);
295
- }
296
-
297
- if (!channelId.startsWith("0x")) {
298
- channelId = "0x" + channelId;
299
- }
300
-
301
- const token = this.currency.tokenAddress || "0x0000000000000000000000000000000000000000";
302
-
303
- const { domain, types, value } = getEthereumClaimTypedData(
304
- channelId,
305
- token,
306
- allowed,
307
- this.chainId,
308
- this.contractAddress
309
- );
310
-
311
- const signature = await this.signer.signTypedData(domain, types, value);
312
-
313
- const claim = {
314
- version: "2",
315
- account: await this.signer.getAddress(),
316
- protocol: this.protocol,
317
- currency: {
318
- code: this.currency.code,
319
- scale: this.currency.scale,
320
- issuer: this.currency.tokenAddress || null
321
- },
322
- destination_account: this.destinationAddress,
323
- authorized_to_claim: allowed,
324
- channel_id: channelId,
325
- signature: signature
326
- };
327
- return Buffer.from(JSON.stringify(claim)).toString("base64");
328
- }
306
+ const token = this.currency.tokenAddress || "0x0000000000000000000000000000000000000000";
307
+
308
+ const { domain, types, value } = getEthereumClaimTypedData(
309
+ channelId,
310
+ token,
311
+ BigInt(allowed),
312
+ this.chainId,
313
+ this.contractAddress
314
+ );
315
+
316
+ const accountString = (await this.walletClient.getAddresses())[0];
317
+ const account = this.walletClient.account || accountString;
318
+ const signature = await this.walletClient.signTypedData({
319
+ account,
320
+ domain,
321
+ types,
322
+ primaryType: 'DhaliClaim',
323
+ message: value
324
+ });
325
+
326
+ const claim = {
327
+ version: "2",
328
+ account: accountString.toLowerCase(),
329
+ protocol: this.currency.network,
330
+ currency: {
331
+ code: this.currency.code,
332
+ scale: this.currency.scale,
333
+ issuer: this.currency.tokenAddress || null
334
+ },
335
+ destination_account: this.destinationAddress.toLowerCase(),
336
+ authorized_to_claim: allowed,
337
+ channel_id: channelId,
338
+ signature: signature
339
+ };
340
+ return Buffer.from(JSON.stringify(claim)).toString("base64");
341
+ }
329
342
  }
330
343
 
331
344
  module.exports = { DhaliEthChannelManager };
345
+