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.
- package/.github/workflows/tests.yaml +1 -1
- package/README.md +120 -61
- package/package.json +2 -2
- package/src/dhali/Currency.js +2 -1
- package/src/dhali/DhaliChannelManager.js +7 -10
- package/src/dhali/DhaliEthChannelManager.js +314 -300
- package/src/dhali/DhaliXrplChannelManager.js +6 -8
- package/src/dhali/configUtils.js +5 -13
- package/src/dhali/utils.js +8 -0
- package/src/index.js +1 -1
- package/tests/DhaliChannelManager.test.js +25 -3
- package/tests/DhaliEthChannelManager.test.js +116 -50
|
@@ -4,7 +4,7 @@ const { sign: signClaim } = require("ripple-keypairs");
|
|
|
4
4
|
|
|
5
5
|
const { fetchPublicConfig, notifyAdminGateway, retrieveChannelIdFromFirestoreRest } = require("./configUtils");
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const { ChannelNotFound } = require("./utils");
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* A management tool for generating payment claims for use with Dhali APIs (XRPL).
|
|
@@ -13,15 +13,13 @@ class DhaliXrplChannelManager {
|
|
|
13
13
|
/**
|
|
14
14
|
* @param {import("xrpl").Wallet} wallet
|
|
15
15
|
* @param {import("xrpl").Client} rpc_client
|
|
16
|
-
* @param {string} protocol
|
|
17
16
|
* @param {import("./Currency")} currency
|
|
18
17
|
* @param {typeof fetch} [httpClient]
|
|
19
18
|
* @param {object} [public_config]
|
|
20
19
|
*/
|
|
21
|
-
constructor(wallet, rpc_client,
|
|
20
|
+
constructor(wallet, rpc_client, currency, httpClient = fetch, public_config) {
|
|
22
21
|
this.wallet = wallet;
|
|
23
22
|
this.rpc_client = rpc_client;
|
|
24
|
-
this.protocol = protocol;
|
|
25
23
|
this.currency = currency;
|
|
26
24
|
this.httpClient = httpClient || fetch;
|
|
27
25
|
this.public_config = public_config;
|
|
@@ -38,7 +36,7 @@ class DhaliXrplChannelManager {
|
|
|
38
36
|
|
|
39
37
|
if (!this.destination) {
|
|
40
38
|
try {
|
|
41
|
-
this.destination = this.public_config.DHALI_PUBLIC_ADDRESSES[this.
|
|
39
|
+
this.destination = this.public_config.DHALI_PUBLIC_ADDRESSES[this.currency.network][this.currency.code].wallet_id;
|
|
42
40
|
} catch (e) {
|
|
43
41
|
// Fallback to default if needed, or throw
|
|
44
42
|
this.destination = "rJiAX3Xk2Fq3KJrjsGajrB5LENZq7VCwAd";
|
|
@@ -53,7 +51,7 @@ class DhaliXrplChannelManager {
|
|
|
53
51
|
*/
|
|
54
52
|
async _retrieveChannelIdFromFirestore() {
|
|
55
53
|
return await retrieveChannelIdFromFirestoreRest(
|
|
56
|
-
this.
|
|
54
|
+
this.currency.network,
|
|
57
55
|
this.currency,
|
|
58
56
|
this.wallet.address,
|
|
59
57
|
this.httpClient
|
|
@@ -147,7 +145,7 @@ class DhaliXrplChannelManager {
|
|
|
147
145
|
currencyIdentifier = `${this.currency.code}.${this.currency.tokenAddress}`;
|
|
148
146
|
}
|
|
149
147
|
await notifyAdminGateway(
|
|
150
|
-
this.
|
|
148
|
+
this.currency.network,
|
|
151
149
|
currencyIdentifier,
|
|
152
150
|
this.wallet.classicAddress,
|
|
153
151
|
channelId,
|
|
@@ -185,7 +183,7 @@ class DhaliXrplChannelManager {
|
|
|
185
183
|
const claim = {
|
|
186
184
|
version: "2",
|
|
187
185
|
account: this.wallet.classicAddress,
|
|
188
|
-
protocol: this.
|
|
186
|
+
protocol: this.currency.network,
|
|
189
187
|
currency: { code: "XRP", scale: 6 },
|
|
190
188
|
destination_account: this.destination,
|
|
191
189
|
authorized_to_claim: allowed.toString(),
|
package/src/dhali/configUtils.js
CHANGED
|
@@ -8,11 +8,8 @@ const Currency = require("./Currency");
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Fetches and parses available Dhali currencies and configurations.
|
|
11
|
-
* @returns {Promise<Object.<string, Object.<string, NetworkCurrencyConfig>>>}
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
11
|
* @param {typeof fetch} [httpClient]
|
|
15
|
-
* @returns {Promise<
|
|
12
|
+
* @returns {Promise<Currency[]>}
|
|
16
13
|
*/
|
|
17
14
|
async function getAvailableDhaliCurrencies(httpClient = fetch) {
|
|
18
15
|
const url = "https://raw.githubusercontent.com/Dhali-org/Dhali-config/master/public.prod.json";
|
|
@@ -28,11 +25,10 @@ async function getAvailableDhaliCurrencies(httpClient = fetch) {
|
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
const publicAddresses = data.DHALI_PUBLIC_ADDRESSES || {};
|
|
31
|
-
/** @type {
|
|
32
|
-
const result =
|
|
28
|
+
/** @type {Currency[]} */
|
|
29
|
+
const result = [];
|
|
33
30
|
|
|
34
31
|
for (const [network, currencies] of Object.entries(publicAddresses)) {
|
|
35
|
-
result[network] = {};
|
|
36
32
|
for (const [code, details] of Object.entries(currencies)) {
|
|
37
33
|
const tokenAddress = details.issuer || null;
|
|
38
34
|
const scale = details.scale || 6;
|
|
@@ -40,12 +36,8 @@ async function getAvailableDhaliCurrencies(httpClient = fetch) {
|
|
|
40
36
|
|
|
41
37
|
if (!destination) continue;
|
|
42
38
|
|
|
43
|
-
const curr = new Currency(code, scale, tokenAddress);
|
|
44
|
-
|
|
45
|
-
result[network][code] = {
|
|
46
|
-
currency: curr,
|
|
47
|
-
destinationAddress: destination
|
|
48
|
-
};
|
|
39
|
+
const curr = new Currency(network, code, scale, tokenAddress);
|
|
40
|
+
result.push(curr);
|
|
49
41
|
}
|
|
50
42
|
}
|
|
51
43
|
return result;
|
package/src/dhali/utils.js
CHANGED
|
@@ -39,6 +39,14 @@ function wrapAsX402PaymentPayload(claimBase64, paymentRequirementBase64) {
|
|
|
39
39
|
return Buffer.from(JSON.stringify(x402Payload)).toString("base64");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
class ChannelNotFound extends Error {
|
|
43
|
+
constructor(message = "No open payment channel found.") {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "ChannelNotFound";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
module.exports = {
|
|
43
50
|
wrapAsX402PaymentPayload,
|
|
51
|
+
ChannelNotFound
|
|
44
52
|
};
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { DhaliChannelManager } = require("./dhali/DhaliChannelManager");
|
|
2
2
|
const { DhaliXrplChannelManager, ChannelNotFound } = require("./dhali/DhaliXrplChannelManager");
|
|
3
3
|
const { DhaliEthChannelManager } = require("./dhali/DhaliEthChannelManager");
|
|
4
|
-
const
|
|
4
|
+
const Currency = require("./dhali/Currency");
|
|
5
5
|
const { getAvailableDhaliCurrencies } = require("./dhali/configUtils");
|
|
6
6
|
const { wrapAsX402PaymentPayload } = require("./dhali/utils");
|
|
7
7
|
|
|
@@ -53,11 +53,11 @@ describe("DhaliChannelManager", () => {
|
|
|
53
53
|
submitAndWait: jest.fn(),
|
|
54
54
|
autofill: jest.fn().mockResolvedValue({}),
|
|
55
55
|
};
|
|
56
|
-
currency = new Currency("XRP", 6);
|
|
56
|
+
currency = new Currency("XRPL.MAINNET", "XRP", 6);
|
|
57
57
|
|
|
58
58
|
const mockHttp = jest.fn();
|
|
59
59
|
configUtils.retrieveChannelIdFromFirestoreRest.mockResolvedValue("CHAN123");
|
|
60
|
-
manager = new DhaliXrplChannelManager(wallet, mockClient,
|
|
60
|
+
manager = new DhaliXrplChannelManager(wallet, mockClient, currency, mockHttp);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
afterEach(() => {
|
|
@@ -82,7 +82,7 @@ describe("DhaliChannelManager", () => {
|
|
|
82
82
|
test("throws ChannelNotFound if firestore returns null", async () => {
|
|
83
83
|
const mockHttp = jest.fn();
|
|
84
84
|
configUtils.retrieveChannelIdFromFirestoreRest.mockResolvedValue(null);
|
|
85
|
-
manager = new DhaliXrplChannelManager(wallet, mockClient,
|
|
85
|
+
manager = new DhaliXrplChannelManager(wallet, mockClient, currency, mockHttp);
|
|
86
86
|
await expect(manager.getAuthToken(100)).rejects.toThrow(ChannelNotFound);
|
|
87
87
|
await expect(manager.getAuthToken(100)).rejects.toThrow(/No open payment channel from/);
|
|
88
88
|
expect(configUtils.retrieveChannelIdFromFirestoreRest).toHaveBeenCalledWith(
|
|
@@ -147,4 +147,26 @@ describe("DhaliChannelManager", () => {
|
|
|
147
147
|
);
|
|
148
148
|
});
|
|
149
149
|
});
|
|
150
|
+
|
|
151
|
+
describe("Casing", () => {
|
|
152
|
+
test("queries Firestore with original XRPL address casing", async () => {
|
|
153
|
+
const mockHttp = jest.fn();
|
|
154
|
+
wallet.address = "rMixedCaseAddress";
|
|
155
|
+
manager = new DhaliXrplChannelManager(
|
|
156
|
+
wallet,
|
|
157
|
+
mockClient,
|
|
158
|
+
currency,
|
|
159
|
+
mockHttp,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await manager._retrieveChannelIdFromFirestore();
|
|
163
|
+
|
|
164
|
+
expect(configUtils.retrieveChannelIdFromFirestoreRest).toHaveBeenCalledWith(
|
|
165
|
+
"XRPL.MAINNET",
|
|
166
|
+
currency,
|
|
167
|
+
"rMixedCaseAddress", // Should NOT be lower-cased
|
|
168
|
+
mockHttp,
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
150
172
|
});
|
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
const { DhaliEthChannelManager } = require("../src/dhali/DhaliEthChannelManager");
|
|
2
2
|
const Currency = require("../src/dhali/Currency");
|
|
3
|
-
const {
|
|
3
|
+
const { keccak256, encodeAbiParameters, parseAbiParameters } = require("viem");
|
|
4
4
|
const configUtils = require("../src/dhali/configUtils");
|
|
5
|
+
const { ChannelNotFound } = require("../src/dhali/utils");
|
|
5
6
|
|
|
6
7
|
jest.mock("../src/dhali/configUtils");
|
|
7
8
|
|
|
8
9
|
describe("DhaliEthChannelManager", () => {
|
|
9
|
-
let
|
|
10
|
-
let
|
|
10
|
+
let mockWalletClient;
|
|
11
|
+
let mockPublicClient;
|
|
11
12
|
let currency;
|
|
12
13
|
let publicConfig;
|
|
13
14
|
let manager;
|
|
14
15
|
|
|
15
16
|
beforeEach(() => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
signTypedData: jest.fn(),
|
|
21
|
-
sendTransaction: jest.fn()
|
|
17
|
+
mockWalletClient = {
|
|
18
|
+
getAddresses: jest.fn().mockResolvedValue(["0x0000000000000000000000000000000000000001"]),
|
|
19
|
+
sendTransaction: jest.fn(),
|
|
20
|
+
signTypedData: jest.fn()
|
|
22
21
|
};
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
mockPublicClient = {
|
|
23
|
+
getGasPrice: jest.fn().mockResolvedValue(BigInt(1000000000)),
|
|
24
|
+
estimateGas: jest.fn().mockResolvedValue(BigInt(21000)),
|
|
25
|
+
getTransactionCount: jest.fn().mockResolvedValue(10),
|
|
26
|
+
waitForTransactionReceipt: jest.fn(),
|
|
27
|
+
call: jest.fn()
|
|
28
|
+
};
|
|
29
|
+
currency = new Currency("ETHEREUM", "ETH", 18);
|
|
25
30
|
publicConfig = {
|
|
26
31
|
DHALI_PUBLIC_ADDRESSES: {
|
|
27
32
|
ETHEREUM: {
|
|
@@ -33,30 +38,30 @@ describe("DhaliEthChannelManager", () => {
|
|
|
33
38
|
}
|
|
34
39
|
};
|
|
35
40
|
configUtils.fetchPublicConfig.mockResolvedValue(publicConfig);
|
|
36
|
-
manager = new DhaliEthChannelManager(
|
|
41
|
+
manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, null, publicConfig);
|
|
37
42
|
jest.clearAllMocks();
|
|
38
43
|
});
|
|
39
44
|
|
|
40
45
|
test("initializes without default http client", () => {
|
|
41
|
-
const localManager = new DhaliEthChannelManager(
|
|
46
|
+
const localManager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, null, publicConfig);
|
|
42
47
|
expect(localManager.httpClient).toBe(fetch);
|
|
43
48
|
expect(localManager.chainId).toBe(1);
|
|
44
49
|
});
|
|
45
50
|
|
|
46
51
|
test("initializes with provided http client", () => {
|
|
47
52
|
const mockHttp = jest.fn();
|
|
48
|
-
const manager = new DhaliEthChannelManager(
|
|
53
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, mockHttp, publicConfig);
|
|
49
54
|
expect(manager.httpClient).toBe(mockHttp);
|
|
50
55
|
});
|
|
51
56
|
|
|
52
57
|
test("initializes without config or addresses (lazy resolution)", () => {
|
|
53
|
-
const manager = new DhaliEthChannelManager(
|
|
58
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, null);
|
|
54
59
|
expect(manager.destinationAddress).toBeUndefined();
|
|
55
60
|
expect(manager.contractAddress).toBeUndefined();
|
|
56
61
|
});
|
|
57
62
|
|
|
58
63
|
test("resolves addresses lazily", async () => {
|
|
59
|
-
const manager = new DhaliEthChannelManager(
|
|
64
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, null);
|
|
60
65
|
|
|
61
66
|
await manager._resolveAddresses();
|
|
62
67
|
|
|
@@ -66,7 +71,7 @@ describe("DhaliEthChannelManager", () => {
|
|
|
66
71
|
});
|
|
67
72
|
|
|
68
73
|
test("resolves destination and contract addresses from provided config", async () => {
|
|
69
|
-
const manager = new DhaliEthChannelManager(
|
|
74
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, null, publicConfig);
|
|
70
75
|
await manager._resolveAddresses();
|
|
71
76
|
expect(manager.destinationAddress).toBe("0x0000000000000000000000000000000000000002");
|
|
72
77
|
expect(manager.contractAddress).toBe("0x0000000000000000000000000000000000000003");
|
|
@@ -74,16 +79,16 @@ describe("DhaliEthChannelManager", () => {
|
|
|
74
79
|
});
|
|
75
80
|
|
|
76
81
|
test("calculates channel ID correctly", async () => {
|
|
77
|
-
const manager = new DhaliEthChannelManager(
|
|
78
|
-
|
|
82
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, null, publicConfig);
|
|
83
|
+
const sender = "0x0000000000000000000000000000000000000001";
|
|
79
84
|
const receiver = "0x0000000000000000000000000000000000000002";
|
|
80
85
|
const token = "0x0000000000000000000000000000000000000003";
|
|
81
86
|
const nonce = 12345n;
|
|
82
87
|
|
|
83
|
-
const expectedId =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
[
|
|
88
|
+
const expectedId = keccak256(
|
|
89
|
+
encodeAbiParameters(
|
|
90
|
+
parseAbiParameters("address, address, address, uint256"),
|
|
91
|
+
[sender, receiver, token, nonce]
|
|
87
92
|
)
|
|
88
93
|
);
|
|
89
94
|
|
|
@@ -98,18 +103,12 @@ describe("DhaliEthChannelManager", () => {
|
|
|
98
103
|
.mockResolvedValueOnce(null) // First poll
|
|
99
104
|
.mockResolvedValueOnce("0x0000000000000000000000000000000000000000000000000000000000000004"); // Second poll
|
|
100
105
|
|
|
101
|
-
const localManager = new DhaliEthChannelManager(
|
|
106
|
+
const localManager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, mockHttp, publicConfig);
|
|
102
107
|
localManager._generateNonce = jest.fn().mockReturnValue(54321n);
|
|
103
108
|
localManager._calculateChannelId = jest.fn().mockReturnValue("0xCalculatedId");
|
|
104
109
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
mockSigner.estimateGas.mockResolvedValue(BigInt(21000));
|
|
108
|
-
mockSigner.sendTransaction.mockResolvedValue({
|
|
109
|
-
wait: jest.fn().mockResolvedValue({ status: 1 })
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
mockProvider.getFeeData = jest.fn().mockResolvedValue({ gasPrice: BigInt(1000000000) });
|
|
110
|
+
mockWalletClient.sendTransaction.mockResolvedValue("0xTxHash");
|
|
111
|
+
mockPublicClient.waitForTransactionReceipt.mockResolvedValue({ status: "success" });
|
|
113
112
|
|
|
114
113
|
configUtils.notifyAdminGateway.mockResolvedValue();
|
|
115
114
|
|
|
@@ -118,7 +117,7 @@ describe("DhaliEthChannelManager", () => {
|
|
|
118
117
|
|
|
119
118
|
const receipt = await localManager.deposit(100);
|
|
120
119
|
|
|
121
|
-
expect(receipt.status).toBe(
|
|
120
|
+
expect(receipt.status).toBe("success");
|
|
122
121
|
expect(configUtils.notifyAdminGateway).toHaveBeenCalledWith(
|
|
123
122
|
"ETHEREUM",
|
|
124
123
|
"ETH",
|
|
@@ -135,12 +134,14 @@ describe("DhaliEthChannelManager", () => {
|
|
|
135
134
|
test("getAuthToken throws if channel not found after polling (REST)", async () => {
|
|
136
135
|
const mockHttp = jest.fn();
|
|
137
136
|
configUtils.retrieveChannelIdFromFirestoreRest.mockResolvedValue(null);
|
|
138
|
-
const manager = new DhaliEthChannelManager(
|
|
137
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, mockHttp, publicConfig);
|
|
139
138
|
|
|
140
139
|
const originalTimeout = global.setTimeout;
|
|
141
140
|
global.setTimeout = (cb) => cb();
|
|
142
141
|
|
|
143
|
-
|
|
142
|
+
const promise = manager.getAuthToken(100);
|
|
143
|
+
await expect(promise).rejects.toThrow(ChannelNotFound);
|
|
144
|
+
await expect(promise).rejects.toThrow(/No open payment channel found in Firestore/);
|
|
144
145
|
|
|
145
146
|
global.setTimeout = originalTimeout;
|
|
146
147
|
});
|
|
@@ -148,18 +149,17 @@ describe("DhaliEthChannelManager", () => {
|
|
|
148
149
|
test("getAuthToken defaults to channel capacity if amount is null", async () => {
|
|
149
150
|
const mockHttp = jest.fn();
|
|
150
151
|
configUtils.retrieveChannelIdFromFirestoreRest.mockResolvedValue("0x0000000000000000000000000000000000000000000000000000000000000005");
|
|
151
|
-
const manager = new DhaliEthChannelManager(
|
|
152
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, mockHttp, publicConfig);
|
|
152
153
|
|
|
153
154
|
// Mock on-chain response for getChannel
|
|
154
155
|
// Selector (4) + 5 words (5 * 32 bytes = 160 bytes = 320 chars)
|
|
155
156
|
// Amount is word 4 (index 4).
|
|
156
157
|
// 0x + 64*4 chars of padding + 5000 in hex (padded to 64 chars)
|
|
157
158
|
const amountHex = BigInt(5000).toString(16).padStart(64, '0');
|
|
158
|
-
const mockResult = "0x" + "0".repeat(64 * 4) + amountHex;
|
|
159
|
-
|
|
159
|
+
const mockResult = { data: "0x" + "0".repeat(64 * 4) + amountHex };
|
|
160
|
+
mockPublicClient.call = jest.fn().mockResolvedValue(mockResult);
|
|
160
161
|
|
|
161
|
-
|
|
162
|
-
mockSigner.signTypedData.mockResolvedValue("0xSignature");
|
|
162
|
+
mockWalletClient.signTypedData.mockResolvedValue("0xSignature");
|
|
163
163
|
|
|
164
164
|
const originalTimeout = global.setTimeout;
|
|
165
165
|
global.setTimeout = (cb) => cb();
|
|
@@ -169,11 +169,42 @@ describe("DhaliEthChannelManager", () => {
|
|
|
169
169
|
|
|
170
170
|
expect(decoded.authorized_to_claim).toBe("5000");
|
|
171
171
|
expect(decoded.channel_id).toBe("0x0000000000000000000000000000000000000000000000000000000000000005");
|
|
172
|
-
expect(
|
|
172
|
+
expect(mockPublicClient.call).toHaveBeenCalled();
|
|
173
173
|
|
|
174
174
|
global.setTimeout = originalTimeout;
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
+
test("deposit notifies admin gateway with lowercase address", async () => {
|
|
178
|
+
const mockHttp = jest.fn();
|
|
179
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, mockHttp, publicConfig);
|
|
180
|
+
|
|
181
|
+
// Setup state for new channel creation
|
|
182
|
+
manager._retrieveChannelIdFromFirestore = jest.fn().mockResolvedValue(null);
|
|
183
|
+
// Provide a valid mixed-case 40-character EIP-55 Ethereum address
|
|
184
|
+
mockWalletClient.getAddresses.mockResolvedValue(["0x71C7656EC7ab88b098defB751B7401B5f6d8976F"]);
|
|
185
|
+
mockWalletClient.sendTransaction = jest.fn().mockResolvedValue("0xhash");
|
|
186
|
+
mockPublicClient.waitForTransactionReceipt = jest.fn().mockResolvedValue({ status: 1 });
|
|
187
|
+
|
|
188
|
+
// Mock polling so deposit finishes
|
|
189
|
+
manager._retrieveChannelIdFromFirestoreWithPolling = jest.fn().mockResolvedValue("0xnewid");
|
|
190
|
+
|
|
191
|
+
// Mock crypto so we can predict channel id or at least verify it's called
|
|
192
|
+
const originalBytes = crypto.randomBytes;
|
|
193
|
+
crypto.randomBytes = jest.fn().mockReturnValue(Buffer.from("00".repeat(32), "hex"));
|
|
194
|
+
|
|
195
|
+
await manager.deposit(100);
|
|
196
|
+
|
|
197
|
+
expect(configUtils.notifyAdminGateway).toHaveBeenCalledWith(
|
|
198
|
+
"ETHEREUM",
|
|
199
|
+
"ETH",
|
|
200
|
+
"0x71c7656ec7ab88b098defb751b7401b5f6d8976f", // Expect perfect lowercase
|
|
201
|
+
expect.any(String),
|
|
202
|
+
mockHttp
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
crypto.randomBytes = originalBytes;
|
|
206
|
+
});
|
|
207
|
+
|
|
177
208
|
test("getAuthToken polls Firestore (REST)", async () => {
|
|
178
209
|
const mockHttp = jest.fn();
|
|
179
210
|
configUtils.retrieveChannelIdFromFirestoreRest
|
|
@@ -181,12 +212,11 @@ describe("DhaliEthChannelManager", () => {
|
|
|
181
212
|
.mockResolvedValueOnce("0x0000000000000000000000000000000000000000000000000000000000000005"); // Second poll
|
|
182
213
|
|
|
183
214
|
const amountHex = BigInt(1000).toString(16).padStart(64, '0');
|
|
184
|
-
const mockResult = "0x" + "0".repeat(64 * 4) + amountHex;
|
|
185
|
-
|
|
215
|
+
const mockResult = { data: "0x" + "0".repeat(64 * 4) + amountHex };
|
|
216
|
+
mockPublicClient.call = jest.fn().mockResolvedValue(mockResult);
|
|
186
217
|
|
|
187
|
-
const manager = new DhaliEthChannelManager(
|
|
188
|
-
|
|
189
|
-
mockSigner.signTypedData.mockResolvedValue("0xSignature");
|
|
218
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, mockHttp, publicConfig);
|
|
219
|
+
mockWalletClient.signTypedData.mockResolvedValue("0xSignature");
|
|
190
220
|
|
|
191
221
|
const originalTimeout = global.setTimeout;
|
|
192
222
|
global.setTimeout = (cb) => cb();
|
|
@@ -201,8 +231,8 @@ describe("DhaliEthChannelManager", () => {
|
|
|
201
231
|
|
|
202
232
|
test("queries Firestore with lowercase address (REST)", async () => {
|
|
203
233
|
const mockHttp = jest.fn();
|
|
204
|
-
const manager = new DhaliEthChannelManager(
|
|
205
|
-
|
|
234
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, mockHttp, publicConfig);
|
|
235
|
+
mockWalletClient.getAddresses.mockResolvedValue(["0xMixEdCaSeAdDrEsS"]);
|
|
206
236
|
|
|
207
237
|
await manager._retrieveChannelIdFromFirestore();
|
|
208
238
|
|
|
@@ -215,8 +245,8 @@ describe("DhaliEthChannelManager", () => {
|
|
|
215
245
|
});
|
|
216
246
|
|
|
217
247
|
test("uses default REST if no function provided", async () => {
|
|
218
|
-
const manager = new DhaliEthChannelManager(
|
|
219
|
-
|
|
248
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, null, publicConfig);
|
|
249
|
+
mockWalletClient.getAddresses.mockResolvedValue(["0xMyAddr"]);
|
|
220
250
|
configUtils.retrieveChannelIdFromFirestoreRest.mockResolvedValue("0xRestId");
|
|
221
251
|
|
|
222
252
|
const id = await manager._retrieveChannelIdFromFirestore();
|
|
@@ -228,4 +258,40 @@ describe("DhaliEthChannelManager", () => {
|
|
|
228
258
|
fetch
|
|
229
259
|
);
|
|
230
260
|
});
|
|
261
|
+
|
|
262
|
+
test("performs lower-casing on EVM addresses consistently", async () => {
|
|
263
|
+
const mixedAddr = "0x71C7656EC7ab88b098defB751B7401B5f6d8976F";
|
|
264
|
+
const lowerAddr = mixedAddr.toLowerCase();
|
|
265
|
+
|
|
266
|
+
const mockHttp = jest.fn();
|
|
267
|
+
const manager = new DhaliEthChannelManager(mockWalletClient, mockPublicClient, currency, mockHttp, publicConfig);
|
|
268
|
+
mockWalletClient.getAddresses.mockResolvedValue([mixedAddr]);
|
|
269
|
+
|
|
270
|
+
// 1. Check firestore retrieval
|
|
271
|
+
configUtils.retrieveChannelIdFromFirestoreRest.mockResolvedValue("0xid");
|
|
272
|
+
await manager._retrieveChannelIdFromFirestore();
|
|
273
|
+
expect(configUtils.retrieveChannelIdFromFirestoreRest).toHaveBeenCalledWith(
|
|
274
|
+
expect.anything(),
|
|
275
|
+
expect.anything(),
|
|
276
|
+
lowerAddr,
|
|
277
|
+
mockHttp
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
// 2. Check gateway notification during deposit (Open Channel path)
|
|
281
|
+
configUtils.retrieveChannelIdFromFirestoreRest.mockResolvedValue(null);
|
|
282
|
+
manager._generateNonce = jest.fn().mockReturnValue(1n);
|
|
283
|
+
manager._calculateChannelId = jest.fn().mockReturnValue("0xid");
|
|
284
|
+
mockWalletClient.sendTransaction.mockResolvedValue("0xhash");
|
|
285
|
+
mockPublicClient.waitForTransactionReceipt.mockResolvedValue({ status: "success" });
|
|
286
|
+
manager._retrieveChannelIdFromFirestoreWithPolling = jest.fn().mockResolvedValue("0xid");
|
|
287
|
+
|
|
288
|
+
await manager.deposit(100);
|
|
289
|
+
expect(configUtils.notifyAdminGateway).toHaveBeenCalledWith(
|
|
290
|
+
expect.anything(),
|
|
291
|
+
expect.anything(),
|
|
292
|
+
lowerAddr,
|
|
293
|
+
expect.anything(),
|
|
294
|
+
mockHttp
|
|
295
|
+
);
|
|
296
|
+
});
|
|
231
297
|
});
|