@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 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
- When a client receives a `402 Payment Required` response, they must:
76
- 1. Create an **atomic split transaction** (99% to developer, 1% to treasury)
77
- 2. Sign and send the transaction
78
- 3. Build the payment header with the signature
79
- 4. Retry the request with the `Authorization` header
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 (including the correct USDC mint for the network):
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
- Use `PaymentBuilder.createAtomicSplitInstructions()` to generate the transfer instructions:
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, // Developer wallet
119
- treasury: requirements.treasury, // Zion treasury
120
- amount: requirements.amount, // Total amount (fee split handled automatically)
121
- asset: requirements.asset, // USDC mint
147
+ recipient: requirements.recipient,
148
+ treasury: requirements.treasury,
149
+ amount: requirements.amount,
150
+ asset: requirements.asset,
122
151
  connection
123
152
  });
124
153
 
125
- // 3. Build transaction (create ATAs if needed)
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 the transaction is confirmed, create the payment header:
161
+ After confirmation, create the payment header and retry the request:
162
162
 
163
163
  ```typescript
164
164
  const paymentHeader = PaymentBuilder.createHeader({
165
- signature, // Transaction 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(), // Unique per request
172
- network: "solana-devnet"
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
  ---
@@ -4,7 +4,11 @@
4
4
  export interface ZionConfig {
5
5
  /** Your API key from the Zion Dashboard */
6
6
  apiKey: string;
7
- /** Price per request in USD (e.g., 0.01 for 1 cent) */
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;
@@ -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
@@ -1,5 +1,6 @@
1
1
  export * from "./Universal";
2
2
  export * from "./ZionGate";
3
+ export * from "./ZionAgent";
3
4
  import { Connection } from "@solana/web3.js";
4
5
  /**
5
6
  * Payment requirements returned in a 402 response.
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.2",
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
+ }