@tanakayuto/intmax402-client 0.1.2 → 0.2.1
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/dist/client.d.ts +11 -4
- package/dist/client.js +61 -9
- package/package.json +6 -5
package/dist/client.d.ts
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
|
+
import { Token } from "intmax2-server-sdk";
|
|
1
2
|
export interface INTMAX402ClientOptions {
|
|
2
3
|
privateKey: string;
|
|
3
4
|
environment?: "mainnet" | "testnet";
|
|
5
|
+
l1RpcUrl?: string;
|
|
4
6
|
}
|
|
5
7
|
export declare class INTMAX402Client {
|
|
6
8
|
private wallet;
|
|
7
9
|
private environment;
|
|
8
10
|
private initialized;
|
|
11
|
+
private intmaxClient;
|
|
9
12
|
constructor(options: INTMAX402ClientOptions);
|
|
10
13
|
init(): Promise<void>;
|
|
14
|
+
initPayment(l1RpcUrl?: string): Promise<void>;
|
|
11
15
|
getAddress(): string;
|
|
16
|
+
getIntMaxAddress(): string;
|
|
12
17
|
getRpcUrl(): string;
|
|
13
|
-
/**
|
|
14
|
-
* Sign a nonce using Ethereum personal_sign (compatible with ethers.verifyMessage).
|
|
15
|
-
*/
|
|
16
18
|
sign(nonce: string): Promise<string>;
|
|
19
|
+
sendPayment(recipientAddress: string, amount: string, token: Token): Promise<{
|
|
20
|
+
txTreeRoot: string;
|
|
21
|
+
transferDigest: string;
|
|
22
|
+
}>;
|
|
17
23
|
/**
|
|
18
24
|
* Fetch a resource, automatically handling 401/402 INTMAX402 challenges.
|
|
19
|
-
*
|
|
25
|
+
* For identity mode: GET -> 401 + nonce -> sign -> GET + Authorization -> 200
|
|
26
|
+
* For payment mode: GET -> 402 + nonce + payment info -> pay + sign -> GET + Authorization (with txHash) -> 200
|
|
20
27
|
*/
|
|
21
28
|
fetch(url: string, options?: RequestInit): Promise<Response>;
|
|
22
29
|
}
|
package/dist/client.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.INTMAX402Client = void 0;
|
|
4
4
|
const ethers_1 = require("ethers");
|
|
5
5
|
const intmax402_core_1 = require("@tanakayuto/intmax402-core");
|
|
6
|
+
const intmax2_server_sdk_1 = require("intmax2-server-sdk");
|
|
6
7
|
const RPC_URLS = {
|
|
7
8
|
mainnet: "https://api.rpc.intmax.io?network=ethereum",
|
|
8
9
|
testnet: "https://sepolia.gateway.tenderly.co",
|
|
@@ -11,29 +12,58 @@ class INTMAX402Client {
|
|
|
11
12
|
wallet;
|
|
12
13
|
environment;
|
|
13
14
|
initialized = false;
|
|
15
|
+
intmaxClient = null;
|
|
14
16
|
constructor(options) {
|
|
17
|
+
// Validate private key before creating wallet
|
|
18
|
+
if (!ethers_1.ethers.isHexString(options.privateKey, 32)) {
|
|
19
|
+
throw new Error("Invalid private key: must be a 32-byte hex string (0x-prefixed)");
|
|
20
|
+
}
|
|
15
21
|
this.wallet = new ethers_1.ethers.Wallet(options.privateKey);
|
|
16
22
|
this.environment = options.environment || "testnet";
|
|
17
23
|
}
|
|
18
24
|
async init() {
|
|
19
|
-
|
|
25
|
+
this.initialized = true;
|
|
26
|
+
}
|
|
27
|
+
async initPayment(l1RpcUrl) {
|
|
28
|
+
this.intmaxClient = new intmax2_server_sdk_1.IntMaxNodeClient({
|
|
29
|
+
environment: this.environment,
|
|
30
|
+
eth_private_key: this.wallet.privateKey,
|
|
31
|
+
l1_rpc_url: l1RpcUrl || RPC_URLS[this.environment],
|
|
32
|
+
loggerLevel: "warn",
|
|
33
|
+
});
|
|
34
|
+
await this.intmaxClient.login();
|
|
20
35
|
this.initialized = true;
|
|
21
36
|
}
|
|
22
37
|
getAddress() {
|
|
23
38
|
return this.wallet.address;
|
|
24
39
|
}
|
|
40
|
+
getIntMaxAddress() {
|
|
41
|
+
if (!this.intmaxClient)
|
|
42
|
+
throw new Error("Call initPayment() first");
|
|
43
|
+
return this.intmaxClient.address;
|
|
44
|
+
}
|
|
25
45
|
getRpcUrl() {
|
|
26
46
|
return RPC_URLS[this.environment];
|
|
27
47
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Sign a nonce using Ethereum personal_sign (compatible with ethers.verifyMessage).
|
|
30
|
-
*/
|
|
31
48
|
async sign(nonce) {
|
|
32
49
|
return await this.wallet.signMessage(nonce);
|
|
33
50
|
}
|
|
51
|
+
async sendPayment(recipientAddress, amount, token) {
|
|
52
|
+
if (!this.intmaxClient) {
|
|
53
|
+
throw new Error("Call initPayment() before sending payments");
|
|
54
|
+
}
|
|
55
|
+
const result = await this.intmaxClient.broadcastTransaction([
|
|
56
|
+
{ address: recipientAddress, amount, token },
|
|
57
|
+
]);
|
|
58
|
+
return {
|
|
59
|
+
txTreeRoot: result.txTreeRoot,
|
|
60
|
+
transferDigest: result.transferDigests[0],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
34
63
|
/**
|
|
35
64
|
* Fetch a resource, automatically handling 401/402 INTMAX402 challenges.
|
|
36
|
-
*
|
|
65
|
+
* For identity mode: GET -> 401 + nonce -> sign -> GET + Authorization -> 200
|
|
66
|
+
* For payment mode: GET -> 402 + nonce + payment info -> pay + sign -> GET + Authorization (with txHash) -> 200
|
|
37
67
|
*/
|
|
38
68
|
async fetch(url, options) {
|
|
39
69
|
const initialResponse = await globalThis.fetch(url, options);
|
|
@@ -46,11 +76,33 @@ class INTMAX402Client {
|
|
|
46
76
|
const challenge = (0, intmax402_core_1.parseWWWAuthenticate)(wwwAuth);
|
|
47
77
|
if (!challenge)
|
|
48
78
|
return initialResponse;
|
|
49
|
-
// Sign the nonce
|
|
50
79
|
const signature = await this.sign(challenge.nonce);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
80
|
+
let authHeader = `INTMAX402 address="${this.wallet.address}", nonce="${challenge.nonce}", signature="${signature}"`;
|
|
81
|
+
// Payment mode: send payment and include txHash
|
|
82
|
+
if (challenge.mode === "payment") {
|
|
83
|
+
if (!this.intmaxClient) {
|
|
84
|
+
throw new Error("Payment required but intmax client not initialized. Call initPayment() first.");
|
|
85
|
+
}
|
|
86
|
+
if (!challenge.serverAddress || !challenge.amount) {
|
|
87
|
+
throw new Error("Server did not provide serverAddress or amount in challenge");
|
|
88
|
+
}
|
|
89
|
+
// Get token list and find matching token
|
|
90
|
+
const tokens = await this.intmaxClient.getTokensList();
|
|
91
|
+
let paymentToken;
|
|
92
|
+
if (challenge.tokenAddress) {
|
|
93
|
+
const found = tokens.find((t) => t.contractAddress.toLowerCase() === challenge.tokenAddress.toLowerCase());
|
|
94
|
+
if (!found) {
|
|
95
|
+
throw new Error(`Token ${challenge.tokenAddress} not found in token list`);
|
|
96
|
+
}
|
|
97
|
+
paymentToken = found;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// Default: native token (index 0)
|
|
101
|
+
paymentToken = tokens.find((t) => t.tokenIndex === 0) || tokens[0];
|
|
102
|
+
}
|
|
103
|
+
const { transferDigest } = await this.sendPayment(challenge.serverAddress, challenge.amount, paymentToken);
|
|
104
|
+
authHeader += `, txHash="${transferDigest}"`;
|
|
105
|
+
}
|
|
54
106
|
return globalThis.fetch(url, {
|
|
55
107
|
...options,
|
|
56
108
|
headers: {
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanakayuto/intmax402-client",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@tanakayuto/intmax402-core": "0.
|
|
8
|
-
"ethers": "^6.0.0"
|
|
7
|
+
"@tanakayuto/intmax402-core": "0.2.0",
|
|
8
|
+
"ethers": "^6.0.0",
|
|
9
|
+
"intmax2-server-sdk": "^1.5.2"
|
|
9
10
|
},
|
|
10
11
|
"devDependencies": {
|
|
11
|
-
"typescript": "^5.4.0",
|
|
12
12
|
"@types/node": "^20.0.0",
|
|
13
|
-
"ethers": "^6.0.0"
|
|
13
|
+
"ethers": "^6.0.0",
|
|
14
|
+
"typescript": "^5.4.0"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"dist",
|