@ziongateway/sdk 0.1.2 → 0.1.4
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/README.md +57 -68
- package/dist/Universal.d.ts +5 -1
- package/dist/ZionAgent.d.ts +185 -0
- package/dist/ZionAgent.js +340 -0
- package/dist/ZionGate.d.ts +1 -1
- package/dist/ZionGate.js +11 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -70,16 +70,56 @@ export async function GET(req: Request) {
|
|
|
70
70
|
|
|
71
71
|
---
|
|
72
72
|
|
|
73
|
+
|
|
73
74
|
## Usage (Client-Side / AI Agent)
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
1.
|
|
77
|
-
2.
|
|
78
|
-
3.
|
|
79
|
-
4.
|
|
76
|
+
The SDK provides a `ZionAgent` class that makes integrating paid APIs as simple as a standard `fetch` call. It automatically handles the entire **402 Payment Required** flow, including:
|
|
77
|
+
1. Detecting 402 responses
|
|
78
|
+
2. Creating and signing the transaction
|
|
79
|
+
3. Submitting to the Solana blockchain
|
|
80
|
+
4. Retrying the request with payment proof
|
|
81
|
+
|
|
82
|
+
### 1. Initialize the Agent
|
|
83
|
+
Initialize the agent with your Solana private key.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { ZionAgent } from "@ziongateway/sdk";
|
|
87
|
+
|
|
88
|
+
const agent = new ZionAgent({
|
|
89
|
+
privateKey: process.env.SOLANA_PRIVATE_KEY!, // Base58 encoded
|
|
90
|
+
rpcUrl: "https://api.mainnet-beta.solana.com" // Optional
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2. Make Requests
|
|
95
|
+
Use `agent.fetch()` exactly like the standard `fetch` API. If payment is required, it happens automatically in the background.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// 1. Just call fetch
|
|
99
|
+
const response = await agent.fetch("https://api.merchant.com/premium-data");
|
|
100
|
+
|
|
101
|
+
// 2. Handle the response
|
|
102
|
+
if (response.ok) {
|
|
103
|
+
const data = await response.json();
|
|
104
|
+
console.log("Received paid content:", data);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Optional: Check if a payment was responsible for this success
|
|
108
|
+
if (response.paymentMade) {
|
|
109
|
+
console.log(`Paid via tx: ${response.txSignature}`);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
That's it! No transaction building, no buffer decoding, no manual retries.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Manual Integration (Advanced)
|
|
118
|
+
|
|
119
|
+
If you prefer to build the transaction manually (or are using a different language), follow these steps when you receive a `402 Payment Required` response:
|
|
80
120
|
|
|
81
121
|
### 1. Handling the 402 Response
|
|
82
|
-
The server returns all payment requirements
|
|
122
|
+
The server returns all payment requirements:
|
|
83
123
|
|
|
84
124
|
```json
|
|
85
125
|
{
|
|
@@ -91,101 +131,50 @@ The server returns all payment requirements (including the correct USDC mint for
|
|
|
91
131
|
}
|
|
92
132
|
```
|
|
93
133
|
|
|
94
|
-
> **Note:** You don't need to know the USDC mint address - it's provided in the response automatically based on the network (mainnet or devnet).
|
|
95
|
-
|
|
96
134
|
### 2. Creating the Atomic Split Transaction
|
|
97
135
|
|
|
98
136
|
The payment **must be split atomically** in a single transaction:
|
|
99
137
|
- **99%** goes to the `recipient` (developer)
|
|
100
138
|
- **1%** goes to the `treasury` (Zion fee)
|
|
101
139
|
|
|
102
|
-
|
|
140
|
+
You can use `PaymentBuilder` to generate the instructions:
|
|
103
141
|
|
|
104
142
|
```typescript
|
|
105
143
|
import { PaymentBuilder } from "@ziongateway/sdk";
|
|
106
|
-
import { Connection, Transaction, PublicKey } from "@solana/web3.js";
|
|
107
|
-
import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction } from "@solana/spl-token";
|
|
108
|
-
|
|
109
|
-
// 1. Receive 402 response with payment requirements
|
|
110
|
-
const res = await fetch("/api/protected");
|
|
111
|
-
if (res.status !== 402) return;
|
|
112
|
-
const requirements = await res.json();
|
|
113
144
|
|
|
114
|
-
// 2. Create atomic split transfer instructions
|
|
115
|
-
const connection = new Connection("https://api.devnet.solana.com");
|
|
116
145
|
const instructions = await PaymentBuilder.createAtomicSplitInstructions({
|
|
117
146
|
sender: wallet.publicKey.toBase58(),
|
|
118
|
-
recipient: requirements.recipient,
|
|
119
|
-
treasury: requirements.treasury,
|
|
120
|
-
amount: requirements.amount,
|
|
121
|
-
asset: requirements.asset,
|
|
147
|
+
recipient: requirements.recipient,
|
|
148
|
+
treasury: requirements.treasury,
|
|
149
|
+
amount: requirements.amount,
|
|
150
|
+
asset: requirements.asset,
|
|
122
151
|
connection
|
|
123
152
|
});
|
|
124
153
|
|
|
125
|
-
|
|
126
|
-
const tx = new Transaction();
|
|
127
|
-
const assetMint = new PublicKey(requirements.asset);
|
|
128
|
-
|
|
129
|
-
// Check if recipient/treasury ATAs exist, create if not
|
|
130
|
-
const recipientAta = await getAssociatedTokenAddress(assetMint, new PublicKey(requirements.recipient));
|
|
131
|
-
const treasuryAta = await getAssociatedTokenAddress(assetMint, new PublicKey(requirements.treasury));
|
|
132
|
-
|
|
133
|
-
const recipientInfo = await connection.getAccountInfo(recipientAta);
|
|
134
|
-
if (!recipientInfo) {
|
|
135
|
-
tx.add(createAssociatedTokenAccountInstruction(
|
|
136
|
-
wallet.publicKey, recipientAta, new PublicKey(requirements.recipient), assetMint
|
|
137
|
-
));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const treasuryInfo = await connection.getAccountInfo(treasuryAta);
|
|
141
|
-
if (!treasuryInfo) {
|
|
142
|
-
tx.add(createAssociatedTokenAccountInstruction(
|
|
143
|
-
wallet.publicKey, treasuryAta, new PublicKey(requirements.treasury), assetMint
|
|
144
|
-
));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Add the split transfer instructions
|
|
148
|
-
tx.add(...instructions);
|
|
149
|
-
|
|
150
|
-
// 4. Sign and send
|
|
151
|
-
const { blockhash } = await connection.getLatestBlockhash();
|
|
152
|
-
tx.recentBlockhash = blockhash;
|
|
153
|
-
tx.feePayer = wallet.publicKey;
|
|
154
|
-
|
|
154
|
+
const tx = new Transaction().add(...instructions);
|
|
155
155
|
const signature = await wallet.sendTransaction(tx, connection);
|
|
156
156
|
await connection.confirmTransaction(signature, "confirmed");
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
### 3. Creating the Payment Header
|
|
160
160
|
|
|
161
|
-
After
|
|
161
|
+
After confirmation, create the payment header and retry the request:
|
|
162
162
|
|
|
163
163
|
```typescript
|
|
164
164
|
const paymentHeader = PaymentBuilder.createHeader({
|
|
165
|
-
signature,
|
|
165
|
+
signature,
|
|
166
166
|
sender: wallet.publicKey.toBase58(),
|
|
167
167
|
recipient: requirements.recipient,
|
|
168
168
|
amount: requirements.amount,
|
|
169
169
|
asset: requirements.asset,
|
|
170
170
|
timestamp: Math.floor(Date.now() / 1000),
|
|
171
|
-
nonce: crypto.randomUUID(),
|
|
172
|
-
network:
|
|
171
|
+
nonce: crypto.randomUUID(),
|
|
172
|
+
network: requirements.network
|
|
173
173
|
});
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### 4. Retry with Authorization Header
|
|
177
174
|
|
|
178
|
-
```typescript
|
|
179
175
|
const response = await fetch("/api/protected", {
|
|
180
|
-
headers: {
|
|
181
|
-
"Authorization": `Bearer ${paymentHeader}`
|
|
182
|
-
}
|
|
176
|
+
headers: { "Authorization": `Bearer ${paymentHeader}` }
|
|
183
177
|
});
|
|
184
|
-
|
|
185
|
-
if (response.ok) {
|
|
186
|
-
const data = await response.json();
|
|
187
|
-
console.log("Success!", data);
|
|
188
|
-
}
|
|
189
178
|
```
|
|
190
179
|
|
|
191
180
|
---
|
package/dist/Universal.d.ts
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
export interface ZionConfig {
|
|
5
5
|
/** Your API key from the Zion Dashboard */
|
|
6
6
|
apiKey: string;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Price per request in USD (e.g., 0.01 for 1 cent).
|
|
9
|
+
* ⚠️ MAX 6 DECIMAL PLACES - USDC has 6 decimal precision.
|
|
10
|
+
* Invalid: 0.0000001 (7 decimals) | Valid: 0.000001 (6 decimals)
|
|
11
|
+
*/
|
|
8
12
|
price: number;
|
|
9
13
|
/** Solana network to use */
|
|
10
14
|
network?: "solana-mainnet" | "solana-devnet";
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import { NetworkName } from "./Universal";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for ZionAgent.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* const agent = new ZionAgent({
|
|
9
|
+
* privateKey: process.env.SOLANA_PRIVATE_KEY!,
|
|
10
|
+
* rpcUrl: "https://mainnet.helius-rpc.com/...",
|
|
11
|
+
* network: "solana-mainnet"
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export interface ZionAgentConfig {
|
|
16
|
+
/**
|
|
17
|
+
* Base58-encoded Solana private key.
|
|
18
|
+
* Get this from your wallet (e.g., Phantom: Settings -> Security -> Export Private Key)
|
|
19
|
+
*/
|
|
20
|
+
privateKey: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional Solana RPC URL.
|
|
23
|
+
* Defaults to public mainnet RPC if not provided.
|
|
24
|
+
* For production, use a dedicated RPC like Helius or QuickNode.
|
|
25
|
+
*/
|
|
26
|
+
rpcUrl?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Network to use for payments.
|
|
29
|
+
* @default "solana-mainnet"
|
|
30
|
+
*/
|
|
31
|
+
network?: NetworkName;
|
|
32
|
+
/**
|
|
33
|
+
* Whether to log debug information.
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
debug?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Options for the fetch request (similar to standard fetch options).
|
|
40
|
+
*/
|
|
41
|
+
export interface ZionFetchOptions {
|
|
42
|
+
/** HTTP method (GET, POST, etc.) */
|
|
43
|
+
method?: string;
|
|
44
|
+
/** Request headers */
|
|
45
|
+
headers?: Record<string, string>;
|
|
46
|
+
/** Request body (will be JSON.stringify'd if object) */
|
|
47
|
+
body?: string | object;
|
|
48
|
+
/** Optional signal for request abortion */
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extended Response with additional payment metadata.
|
|
53
|
+
*/
|
|
54
|
+
export interface ZionResponse {
|
|
55
|
+
/** Whether the request was successful (status 200-299) */
|
|
56
|
+
ok: boolean;
|
|
57
|
+
/** HTTP status code */
|
|
58
|
+
status: number;
|
|
59
|
+
/** Status text */
|
|
60
|
+
statusText: string;
|
|
61
|
+
/** Response headers */
|
|
62
|
+
headers: Headers;
|
|
63
|
+
/** Whether a payment was made for this request */
|
|
64
|
+
paymentMade: boolean;
|
|
65
|
+
/** Transaction signature if payment was made */
|
|
66
|
+
txSignature?: string;
|
|
67
|
+
/** Parse response as JSON */
|
|
68
|
+
json(): Promise<any>;
|
|
69
|
+
/** Parse response as text */
|
|
70
|
+
text(): Promise<string>;
|
|
71
|
+
/** Raw response object */
|
|
72
|
+
raw: Response;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Error codes for ZionAgent errors.
|
|
76
|
+
*/
|
|
77
|
+
export type ZionErrorCode = 'INSUFFICIENT_FUNDS' | 'TX_FAILED' | 'TX_TIMEOUT' | 'NETWORK_ERROR' | 'INVALID_REQUIREMENTS' | 'INVALID_PRIVATE_KEY' | 'PAYMENT_REJECTED';
|
|
78
|
+
/**
|
|
79
|
+
* Custom error class for ZionAgent payment errors.
|
|
80
|
+
*/
|
|
81
|
+
export declare class ZionPaymentError extends Error {
|
|
82
|
+
/** Error code for programmatic handling */
|
|
83
|
+
code: ZionErrorCode;
|
|
84
|
+
/** Additional error details */
|
|
85
|
+
details?: any;
|
|
86
|
+
constructor(message: string, code: ZionErrorCode, details?: any);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* ZionAgent - Client-side SDK for AI Agents to make paid API requests.
|
|
90
|
+
*
|
|
91
|
+
* Provides a drop-in replacement for `fetch()` that automatically handles
|
|
92
|
+
* x402 payment flows. When a server responds with 402 Payment Required,
|
|
93
|
+
* ZionAgent will:
|
|
94
|
+
* 1. Parse the payment requirements
|
|
95
|
+
* 2. Create and sign a Solana transaction
|
|
96
|
+
* 3. Submit the transaction and wait for confirmation
|
|
97
|
+
* 4. Retry the request with the payment proof
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* import { ZionAgent } from "@ziongateway/sdk";
|
|
102
|
+
*
|
|
103
|
+
* const agent = new ZionAgent({
|
|
104
|
+
* privateKey: process.env.SOLANA_PRIVATE_KEY!,
|
|
105
|
+
* rpcUrl: "https://mainnet.helius-rpc.com/..."
|
|
106
|
+
* });
|
|
107
|
+
*
|
|
108
|
+
* // Just like fetch - payment happens automatically!
|
|
109
|
+
* const response = await agent.fetch("https://api.merchant.com/premium-data");
|
|
110
|
+
* const data = await response.json();
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare class ZionAgent {
|
|
114
|
+
private wallet;
|
|
115
|
+
private connection;
|
|
116
|
+
private network;
|
|
117
|
+
private debug;
|
|
118
|
+
/**
|
|
119
|
+
* Create a new ZionAgent instance.
|
|
120
|
+
*
|
|
121
|
+
* @param config - Configuration options
|
|
122
|
+
* @throws {ZionPaymentError} If private key is invalid
|
|
123
|
+
*/
|
|
124
|
+
constructor(config: ZionAgentConfig);
|
|
125
|
+
/**
|
|
126
|
+
* Get the agent's wallet public key.
|
|
127
|
+
*/
|
|
128
|
+
get publicKey(): PublicKey;
|
|
129
|
+
/**
|
|
130
|
+
* Get the agent's wallet address as a string.
|
|
131
|
+
*/
|
|
132
|
+
get address(): string;
|
|
133
|
+
/**
|
|
134
|
+
* Make a fetch request with automatic x402 payment handling.
|
|
135
|
+
*
|
|
136
|
+
* This method works just like the standard `fetch()` API, but automatically
|
|
137
|
+
* handles 402 Payment Required responses by:
|
|
138
|
+
* 1. Parsing payment requirements from the response
|
|
139
|
+
* 2. Creating and signing a Solana transaction
|
|
140
|
+
* 3. Submitting the payment to the blockchain
|
|
141
|
+
* 4. Retrying the request with payment proof
|
|
142
|
+
*
|
|
143
|
+
* @param url - The URL to fetch
|
|
144
|
+
* @param options - Optional fetch options (method, headers, body)
|
|
145
|
+
* @returns A ZionResponse with the result
|
|
146
|
+
*
|
|
147
|
+
* @throws {ZionPaymentError} If payment fails (insufficient funds, tx error, etc.)
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* // Simple GET request
|
|
152
|
+
* const response = await agent.fetch("https://api.merchant.com/data");
|
|
153
|
+
*
|
|
154
|
+
* // POST request with body
|
|
155
|
+
* const response = await agent.fetch("https://api.merchant.com/action", {
|
|
156
|
+
* method: "POST",
|
|
157
|
+
* headers: { "Content-Type": "application/json" },
|
|
158
|
+
* body: { query: "What is the weather?" }
|
|
159
|
+
* });
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
fetch(url: string, options?: ZionFetchOptions): Promise<ZionResponse>;
|
|
163
|
+
/**
|
|
164
|
+
* Parse payment requirements from a 402 response.
|
|
165
|
+
*/
|
|
166
|
+
private parsePaymentRequirements;
|
|
167
|
+
/**
|
|
168
|
+
* Execute the payment transaction.
|
|
169
|
+
*/
|
|
170
|
+
private executePayment;
|
|
171
|
+
/**
|
|
172
|
+
* Generate a random nonce for the payment header.
|
|
173
|
+
*/
|
|
174
|
+
private generateNonce;
|
|
175
|
+
/**
|
|
176
|
+
* Wrap a Response object into a ZionResponse.
|
|
177
|
+
*/
|
|
178
|
+
private wrapResponse;
|
|
179
|
+
/**
|
|
180
|
+
* Get the USDC balance for this agent's wallet.
|
|
181
|
+
*
|
|
182
|
+
* @returns The USDC balance in human-readable format (e.g., "10.50")
|
|
183
|
+
*/
|
|
184
|
+
getBalance(): Promise<string>;
|
|
185
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ZionAgent = exports.ZionPaymentError = void 0;
|
|
7
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
8
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
9
|
+
const bs58_1 = __importDefault(require("bs58"));
|
|
10
|
+
const index_1 = require("./index");
|
|
11
|
+
const Universal_1 = require("./Universal");
|
|
12
|
+
/**
|
|
13
|
+
* Custom error class for ZionAgent payment errors.
|
|
14
|
+
*/
|
|
15
|
+
class ZionPaymentError extends Error {
|
|
16
|
+
constructor(message, code, details) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'ZionPaymentError';
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.details = details;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.ZionPaymentError = ZionPaymentError;
|
|
24
|
+
// ==================== ZionAgent Class ====================
|
|
25
|
+
/**
|
|
26
|
+
* ZionAgent - Client-side SDK for AI Agents to make paid API requests.
|
|
27
|
+
*
|
|
28
|
+
* Provides a drop-in replacement for `fetch()` that automatically handles
|
|
29
|
+
* x402 payment flows. When a server responds with 402 Payment Required,
|
|
30
|
+
* ZionAgent will:
|
|
31
|
+
* 1. Parse the payment requirements
|
|
32
|
+
* 2. Create and sign a Solana transaction
|
|
33
|
+
* 3. Submit the transaction and wait for confirmation
|
|
34
|
+
* 4. Retry the request with the payment proof
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { ZionAgent } from "@ziongateway/sdk";
|
|
39
|
+
*
|
|
40
|
+
* const agent = new ZionAgent({
|
|
41
|
+
* privateKey: process.env.SOLANA_PRIVATE_KEY!,
|
|
42
|
+
* rpcUrl: "https://mainnet.helius-rpc.com/..."
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* // Just like fetch - payment happens automatically!
|
|
46
|
+
* const response = await agent.fetch("https://api.merchant.com/premium-data");
|
|
47
|
+
* const data = await response.json();
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
class ZionAgent {
|
|
51
|
+
/**
|
|
52
|
+
* Create a new ZionAgent instance.
|
|
53
|
+
*
|
|
54
|
+
* @param config - Configuration options
|
|
55
|
+
* @throws {ZionPaymentError} If private key is invalid
|
|
56
|
+
*/
|
|
57
|
+
constructor(config) {
|
|
58
|
+
// Validate and decode private key
|
|
59
|
+
try {
|
|
60
|
+
this.wallet = web3_js_1.Keypair.fromSecretKey(bs58_1.default.decode(config.privateKey));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new ZionPaymentError("Invalid private key. Ensure it's a valid base58-encoded Solana private key.", 'INVALID_PRIVATE_KEY');
|
|
64
|
+
}
|
|
65
|
+
// Set up connection
|
|
66
|
+
this.network = config.network || "solana-mainnet";
|
|
67
|
+
const defaultRpc = this.network === "solana-devnet"
|
|
68
|
+
? "https://api.devnet.solana.com"
|
|
69
|
+
: "https://api.mainnet-beta.solana.com";
|
|
70
|
+
this.connection = new web3_js_1.Connection(config.rpcUrl || defaultRpc, "confirmed");
|
|
71
|
+
this.debug = config.debug || false;
|
|
72
|
+
if (this.debug) {
|
|
73
|
+
console.log(`[ZionAgent] Initialized with wallet: ${this.wallet.publicKey.toBase58()}`);
|
|
74
|
+
console.log(`[ZionAgent] Network: ${this.network}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get the agent's wallet public key.
|
|
79
|
+
*/
|
|
80
|
+
get publicKey() {
|
|
81
|
+
return this.wallet.publicKey;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the agent's wallet address as a string.
|
|
85
|
+
*/
|
|
86
|
+
get address() {
|
|
87
|
+
return this.wallet.publicKey.toBase58();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Make a fetch request with automatic x402 payment handling.
|
|
91
|
+
*
|
|
92
|
+
* This method works just like the standard `fetch()` API, but automatically
|
|
93
|
+
* handles 402 Payment Required responses by:
|
|
94
|
+
* 1. Parsing payment requirements from the response
|
|
95
|
+
* 2. Creating and signing a Solana transaction
|
|
96
|
+
* 3. Submitting the payment to the blockchain
|
|
97
|
+
* 4. Retrying the request with payment proof
|
|
98
|
+
*
|
|
99
|
+
* @param url - The URL to fetch
|
|
100
|
+
* @param options - Optional fetch options (method, headers, body)
|
|
101
|
+
* @returns A ZionResponse with the result
|
|
102
|
+
*
|
|
103
|
+
* @throws {ZionPaymentError} If payment fails (insufficient funds, tx error, etc.)
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* // Simple GET request
|
|
108
|
+
* const response = await agent.fetch("https://api.merchant.com/data");
|
|
109
|
+
*
|
|
110
|
+
* // POST request with body
|
|
111
|
+
* const response = await agent.fetch("https://api.merchant.com/action", {
|
|
112
|
+
* method: "POST",
|
|
113
|
+
* headers: { "Content-Type": "application/json" },
|
|
114
|
+
* body: { query: "What is the weather?" }
|
|
115
|
+
* });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
async fetch(url, options = {}) {
|
|
119
|
+
// Prepare request options
|
|
120
|
+
const fetchOptions = {
|
|
121
|
+
method: options.method || "GET",
|
|
122
|
+
headers: { ...options.headers },
|
|
123
|
+
signal: options.signal
|
|
124
|
+
};
|
|
125
|
+
// Handle body
|
|
126
|
+
if (options.body) {
|
|
127
|
+
if (typeof options.body === "object") {
|
|
128
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
129
|
+
fetchOptions.headers["Content-Type"] = "application/json";
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
fetchOptions.body = options.body;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (this.debug) {
|
|
136
|
+
console.log(`[ZionAgent] Fetching: ${url}`);
|
|
137
|
+
}
|
|
138
|
+
// Make initial request
|
|
139
|
+
const initialResponse = await fetch(url, fetchOptions);
|
|
140
|
+
// If not 402, return the response as-is
|
|
141
|
+
if (initialResponse.status !== 402) {
|
|
142
|
+
return this.wrapResponse(initialResponse, false);
|
|
143
|
+
}
|
|
144
|
+
if (this.debug) {
|
|
145
|
+
console.log(`[ZionAgent] Received 402 Payment Required`);
|
|
146
|
+
}
|
|
147
|
+
// Parse payment requirements
|
|
148
|
+
const requirements = await this.parsePaymentRequirements(initialResponse);
|
|
149
|
+
if (this.debug) {
|
|
150
|
+
console.log(`[ZionAgent] Payment requirements:`, requirements);
|
|
151
|
+
}
|
|
152
|
+
// Execute payment
|
|
153
|
+
const { signature, paymentHeader } = await this.executePayment(requirements);
|
|
154
|
+
if (this.debug) {
|
|
155
|
+
console.log(`[ZionAgent] Payment successful! Signature: ${signature}`);
|
|
156
|
+
}
|
|
157
|
+
// Retry with payment proof
|
|
158
|
+
const retryOptions = {
|
|
159
|
+
...fetchOptions,
|
|
160
|
+
headers: {
|
|
161
|
+
...fetchOptions.headers,
|
|
162
|
+
"Authorization": `Bearer ${paymentHeader}`
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const finalResponse = await fetch(url, retryOptions);
|
|
166
|
+
// Check if payment was rejected
|
|
167
|
+
if (finalResponse.status === 402 || finalResponse.status === 403) {
|
|
168
|
+
const errorBody = await finalResponse.text();
|
|
169
|
+
throw new ZionPaymentError(`Payment was rejected by the server: ${errorBody}`, 'PAYMENT_REJECTED', { status: finalResponse.status, body: errorBody });
|
|
170
|
+
}
|
|
171
|
+
return this.wrapResponse(finalResponse, true, signature);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Parse payment requirements from a 402 response.
|
|
175
|
+
*/
|
|
176
|
+
async parsePaymentRequirements(response) {
|
|
177
|
+
let body;
|
|
178
|
+
try {
|
|
179
|
+
body = await response.json();
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
throw new ZionPaymentError("Failed to parse payment requirements from 402 response", 'INVALID_REQUIREMENTS', { error });
|
|
183
|
+
}
|
|
184
|
+
// Validate required fields
|
|
185
|
+
const required = ['recipient', 'amount', 'asset', 'treasury'];
|
|
186
|
+
for (const field of required) {
|
|
187
|
+
if (!body[field]) {
|
|
188
|
+
throw new ZionPaymentError(`Missing required field in payment requirements: ${field}`, 'INVALID_REQUIREMENTS', { body });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
recipient: body.recipient,
|
|
193
|
+
amount: body.amount,
|
|
194
|
+
asset: body.asset,
|
|
195
|
+
treasury: body.treasury,
|
|
196
|
+
network: body.network || this.network,
|
|
197
|
+
feeBps: body.feeBps || 100
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Execute the payment transaction.
|
|
202
|
+
*/
|
|
203
|
+
async executePayment(requirements) {
|
|
204
|
+
const assetMint = new web3_js_1.PublicKey(requirements.asset);
|
|
205
|
+
const recipient = new web3_js_1.PublicKey(requirements.recipient);
|
|
206
|
+
const treasury = new web3_js_1.PublicKey(requirements.treasury);
|
|
207
|
+
// Get Associated Token Addresses
|
|
208
|
+
const senderAta = await (0, spl_token_1.getAssociatedTokenAddress)(assetMint, this.wallet.publicKey);
|
|
209
|
+
const recipientAta = await (0, spl_token_1.getAssociatedTokenAddress)(assetMint, recipient);
|
|
210
|
+
const treasuryAta = await (0, spl_token_1.getAssociatedTokenAddress)(assetMint, treasury);
|
|
211
|
+
// Check sender balance
|
|
212
|
+
try {
|
|
213
|
+
const balance = await this.connection.getTokenAccountBalance(senderAta);
|
|
214
|
+
const requiredAmount = BigInt(requirements.amount);
|
|
215
|
+
const availableAmount = BigInt(balance.value.amount);
|
|
216
|
+
if (availableAmount < requiredAmount) {
|
|
217
|
+
const requiredUsd = Number(requiredAmount) / 1000000;
|
|
218
|
+
const availableUsd = Number(availableAmount) / 1000000;
|
|
219
|
+
throw new ZionPaymentError(`Insufficient USDC balance. Required: $${requiredUsd.toFixed(6)}, Available: $${availableUsd.toFixed(6)}`, 'INSUFFICIENT_FUNDS', { required: requiredAmount.toString(), available: availableAmount.toString() });
|
|
220
|
+
}
|
|
221
|
+
if (this.debug) {
|
|
222
|
+
console.log(`[ZionAgent] Balance check passed. Available: ${balance.value.uiAmountString} USDC`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (error instanceof ZionPaymentError)
|
|
227
|
+
throw error;
|
|
228
|
+
throw new ZionPaymentError("No USDC token account found. Fund your wallet with USDC first.", 'INSUFFICIENT_FUNDS', { wallet: this.wallet.publicKey.toBase58() });
|
|
229
|
+
}
|
|
230
|
+
// Build transaction
|
|
231
|
+
const tx = new web3_js_1.Transaction();
|
|
232
|
+
// Create recipient ATA if needed
|
|
233
|
+
const recipientInfo = await this.connection.getAccountInfo(recipientAta);
|
|
234
|
+
if (!recipientInfo) {
|
|
235
|
+
if (this.debug)
|
|
236
|
+
console.log(`[ZionAgent] Creating recipient ATA...`);
|
|
237
|
+
tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(this.wallet.publicKey, recipientAta, recipient, assetMint));
|
|
238
|
+
}
|
|
239
|
+
// Create treasury ATA if needed
|
|
240
|
+
const treasuryInfo = await this.connection.getAccountInfo(treasuryAta);
|
|
241
|
+
if (!treasuryInfo) {
|
|
242
|
+
if (this.debug)
|
|
243
|
+
console.log(`[ZionAgent] Creating treasury ATA...`);
|
|
244
|
+
tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(this.wallet.publicKey, treasuryAta, treasury, assetMint));
|
|
245
|
+
}
|
|
246
|
+
// Add transfer instructions (atomic split)
|
|
247
|
+
const instructions = await index_1.PaymentBuilder.createAtomicSplitInstructions({
|
|
248
|
+
sender: this.wallet.publicKey.toBase58(),
|
|
249
|
+
recipient: requirements.recipient,
|
|
250
|
+
treasury: requirements.treasury,
|
|
251
|
+
amount: requirements.amount,
|
|
252
|
+
asset: requirements.asset,
|
|
253
|
+
connection: this.connection
|
|
254
|
+
});
|
|
255
|
+
tx.add(...instructions);
|
|
256
|
+
// Sign and send
|
|
257
|
+
try {
|
|
258
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash();
|
|
259
|
+
tx.recentBlockhash = blockhash;
|
|
260
|
+
tx.feePayer = this.wallet.publicKey;
|
|
261
|
+
tx.sign(this.wallet);
|
|
262
|
+
if (this.debug)
|
|
263
|
+
console.log(`[ZionAgent] Sending transaction...`);
|
|
264
|
+
const signature = await this.connection.sendRawTransaction(tx.serialize());
|
|
265
|
+
if (this.debug)
|
|
266
|
+
console.log(`[ZionAgent] Confirming transaction: ${signature}`);
|
|
267
|
+
// Wait for confirmation with timeout
|
|
268
|
+
const confirmation = await this.connection.confirmTransaction({
|
|
269
|
+
signature,
|
|
270
|
+
blockhash,
|
|
271
|
+
lastValidBlockHeight
|
|
272
|
+
}, "confirmed");
|
|
273
|
+
if (confirmation.value.err) {
|
|
274
|
+
throw new ZionPaymentError(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`, 'TX_FAILED', { signature, error: confirmation.value.err });
|
|
275
|
+
}
|
|
276
|
+
// Create payment header
|
|
277
|
+
const paymentHeader = index_1.PaymentBuilder.createHeader({
|
|
278
|
+
signature,
|
|
279
|
+
sender: this.wallet.publicKey.toBase58(),
|
|
280
|
+
recipient: requirements.recipient,
|
|
281
|
+
amount: requirements.amount,
|
|
282
|
+
asset: requirements.asset,
|
|
283
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
284
|
+
nonce: this.generateNonce(),
|
|
285
|
+
network: requirements.network || this.network
|
|
286
|
+
});
|
|
287
|
+
return { signature, paymentHeader };
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
if (error instanceof ZionPaymentError)
|
|
291
|
+
throw error;
|
|
292
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
293
|
+
if (message.includes('timeout') || message.includes('expired')) {
|
|
294
|
+
throw new ZionPaymentError(`Transaction timed out. It may still succeed - check: ${message}`, 'TX_TIMEOUT', { error: message });
|
|
295
|
+
}
|
|
296
|
+
throw new ZionPaymentError(`Transaction failed: ${message}`, 'TX_FAILED', { error: message });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Generate a random nonce for the payment header.
|
|
301
|
+
*/
|
|
302
|
+
generateNonce() {
|
|
303
|
+
return Math.random().toString(36).substring(2, 15) +
|
|
304
|
+
Math.random().toString(36).substring(2, 15);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Wrap a Response object into a ZionResponse.
|
|
308
|
+
*/
|
|
309
|
+
wrapResponse(response, paymentMade, txSignature) {
|
|
310
|
+
return {
|
|
311
|
+
ok: response.ok,
|
|
312
|
+
status: response.status,
|
|
313
|
+
statusText: response.statusText,
|
|
314
|
+
headers: response.headers,
|
|
315
|
+
paymentMade,
|
|
316
|
+
txSignature,
|
|
317
|
+
json: () => response.json(),
|
|
318
|
+
text: () => response.text(),
|
|
319
|
+
raw: response
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get the USDC balance for this agent's wallet.
|
|
324
|
+
*
|
|
325
|
+
* @returns The USDC balance in human-readable format (e.g., "10.50")
|
|
326
|
+
*/
|
|
327
|
+
async getBalance() {
|
|
328
|
+
const networkConfig = Universal_1.NETWORKS[this.network];
|
|
329
|
+
const mintPubkey = new web3_js_1.PublicKey(networkConfig.usdcMint);
|
|
330
|
+
const ata = await (0, spl_token_1.getAssociatedTokenAddress)(mintPubkey, this.wallet.publicKey);
|
|
331
|
+
try {
|
|
332
|
+
const balance = await this.connection.getTokenAccountBalance(ata);
|
|
333
|
+
return balance.value.uiAmountString || "0";
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return "0";
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
exports.ZionAgent = ZionAgent;
|
package/dist/ZionGate.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare class ZionGate {
|
|
|
26
26
|
/**
|
|
27
27
|
* Create a new ZionGate instance.
|
|
28
28
|
* @param config - Configuration options
|
|
29
|
-
* @throws Error if price is not positive
|
|
29
|
+
* @throws Error if price is not positive or has more than 6 decimal places
|
|
30
30
|
*/
|
|
31
31
|
constructor(config: ZionConfig);
|
|
32
32
|
/**
|
package/dist/ZionGate.js
CHANGED
|
@@ -29,15 +29,24 @@ class ZionGate {
|
|
|
29
29
|
/**
|
|
30
30
|
* Create a new ZionGate instance.
|
|
31
31
|
* @param config - Configuration options
|
|
32
|
-
* @throws Error if price is not positive
|
|
32
|
+
* @throws Error if price is not positive or has more than 6 decimal places
|
|
33
33
|
*/
|
|
34
34
|
constructor(config) {
|
|
35
35
|
this.developerWallet = null;
|
|
36
36
|
this.isInitialized = false;
|
|
37
|
-
// Validate price
|
|
37
|
+
// Validate price is positive
|
|
38
38
|
if (!config.price || config.price <= 0) {
|
|
39
39
|
throw new Error("ZionGate: Price must be a positive number");
|
|
40
40
|
}
|
|
41
|
+
// Validate price has max 6 decimal places (USDC precision)
|
|
42
|
+
const priceStr = config.price.toString();
|
|
43
|
+
const decimalIndex = priceStr.indexOf('.');
|
|
44
|
+
if (decimalIndex !== -1) {
|
|
45
|
+
const decimals = priceStr.length - decimalIndex - 1;
|
|
46
|
+
if (decimals > 6) {
|
|
47
|
+
throw new Error("ZionGate: Price cannot have more than 6 decimal places (USDC precision)");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
41
50
|
this.config = config;
|
|
42
51
|
const defaults = Universal_1.NETWORKS[config.network || "solana-mainnet"];
|
|
43
52
|
this.api = axios_1.default.create({
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.PaymentBuilder = void 0;
|
|
40
40
|
__exportStar(require("./Universal"), exports);
|
|
41
41
|
__exportStar(require("./ZionGate"), exports);
|
|
42
|
+
__exportStar(require("./ZionAgent"), exports);
|
|
42
43
|
const web3_js_1 = require("@solana/web3.js");
|
|
43
44
|
/**
|
|
44
45
|
* PaymentBuilder - Client-side utilities for AI Agents and wallets.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziongateway/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "TypeScript SDK for Zion x402 Facilitator",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@solana/spl-token": "^0.4.14",
|
|
25
25
|
"@solana/web3.js": "^1.98.4",
|
|
26
|
-
"axios": "^1.6.0"
|
|
26
|
+
"axios": "^1.6.0",
|
|
27
|
+
"bs58": "^6.0.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/bs58": "^4.0.4",
|
|
@@ -38,4 +39,4 @@
|
|
|
38
39
|
"README.md"
|
|
39
40
|
],
|
|
40
41
|
"homepage": "https://docs.ziongateway.xyz"
|
|
41
|
-
}
|
|
42
|
+
}
|