gokite-aa-sdk 1.0.9 → 1.0.11

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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Buy Content Script
3
+ *
4
+ * This script demonstrates the complete x402 payment flow:
5
+ * 1. Request protected content (receive 402 + PaymentRequirements)
6
+ * 2. Sign EIP-712 transfer authorization
7
+ * 3. Send payment via X-Payment header
8
+ * 4. Receive content (service verifies & settles via facilitator)
9
+ *
10
+ * Usage:
11
+ * npm run buy
12
+ *
13
+ * Environment variables (from .env.local):
14
+ * AA_WALLET - AA wallet address
15
+ * SESSION_ID - Session ID for this agent
16
+ * AGENT_PRIVATE_KEY - Agent private key (signs authorizations)
17
+ * SERVICE_URL - Protected service URL
18
+ * FACILITATOR_URL - x402 facilitator URL (for health check)
19
+ */
20
+ import "dotenv/config";
@@ -0,0 +1,285 @@
1
+ "use strict";
2
+ /**
3
+ * Buy Content Script
4
+ *
5
+ * This script demonstrates the complete x402 payment flow:
6
+ * 1. Request protected content (receive 402 + PaymentRequirements)
7
+ * 2. Sign EIP-712 transfer authorization
8
+ * 3. Send payment via X-Payment header
9
+ * 4. Receive content (service verifies & settles via facilitator)
10
+ *
11
+ * Usage:
12
+ * npm run buy
13
+ *
14
+ * Environment variables (from .env.local):
15
+ * AA_WALLET - AA wallet address
16
+ * SESSION_ID - Session ID for this agent
17
+ * AGENT_PRIVATE_KEY - Agent private key (signs authorizations)
18
+ * SERVICE_URL - Protected service URL
19
+ * FACILITATOR_URL - x402 facilitator URL (for health check)
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ const ethers_1 = require("ethers");
23
+ require("dotenv/config");
24
+ // =============================================================================
25
+ // Configuration
26
+ // =============================================================================
27
+ const CHAIN_ID = 2368n;
28
+ const RPC_URL = "https://rpc-testnet.gokite.ai";
29
+ // From environment
30
+ const AA_WALLET = process.env.AA_WALLET;
31
+ const SESSION_ID = process.env.SESSION_ID;
32
+ const AGENT_PRIVATE_KEY = process.env.AGENT_PRIVATE_KEY;
33
+ const SERVICE_URL = process.env.SERVICE_URL || "http://localhost:8099";
34
+ const FACILITATOR_URL = process.env.FACILITATOR_URL || "http://localhost:8080";
35
+ // Validate environment
36
+ if (!AA_WALLET || !SESSION_ID || !AGENT_PRIVATE_KEY) {
37
+ console.error("❌ Missing environment variables. Please run setup first:");
38
+ console.error(" npm run setup");
39
+ console.error("\n Required: AA_WALLET, SESSION_ID, AGENT_PRIVATE_KEY");
40
+ process.exit(1);
41
+ }
42
+ const agentSigner = new ethers_1.ethers.Wallet(AGENT_PRIVATE_KEY);
43
+ // =============================================================================
44
+ // EIP-712 Signing
45
+ // =============================================================================
46
+ const TRANSFER_AUTH_TYPES = {
47
+ TransferWithAuthorization: [
48
+ { name: "from", type: "address" },
49
+ { name: "to", type: "address" },
50
+ { name: "token", type: "address" },
51
+ { name: "value", type: "uint256" },
52
+ { name: "validAfter", type: "uint256" },
53
+ { name: "validBefore", type: "uint256" },
54
+ { name: "nonce", type: "bytes32" },
55
+ ],
56
+ };
57
+ function getEIP712Domain(aaWallet, chainId) {
58
+ return {
59
+ name: "GokiteAccount",
60
+ version: "1",
61
+ chainId: chainId,
62
+ verifyingContract: aaWallet,
63
+ };
64
+ }
65
+ async function signTransferAuthorization(aaWallet, chainId, auth) {
66
+ const domain = getEIP712Domain(aaWallet, chainId);
67
+ return agentSigner.signTypedData(domain, TRANSFER_AUTH_TYPES, auth);
68
+ }
69
+ // =============================================================================
70
+ // Payment Flow Functions
71
+ // =============================================================================
72
+ /**
73
+ * Step 1: Request content without payment
74
+ * Expected: 402 Payment Required with PaymentRequirements
75
+ */
76
+ async function requestContent(endpoint) {
77
+ console.log("\n Step 1: Request Content (without payment)");
78
+ console.log("─".repeat(50));
79
+ console.log(` URL: ${SERVICE_URL}${endpoint}`);
80
+ const response = await fetch(`${SERVICE_URL}${endpoint}`);
81
+ if (response.status === 402) {
82
+ const data = (await response.json());
83
+ console.log(` Status: 402 Payment Required ✅`);
84
+ // Find kite-testnet payment option (we'll use gokite-aa scheme to pay)
85
+ const kiteOption = data.accepts?.find((req) => req.network === "kite-testnet");
86
+ if (kiteOption) {
87
+ console.log(` Payment options: ${data.accepts?.length || 0}`);
88
+ console.log(` Selected: ${kiteOption.network} (using gokite-aa scheme)`);
89
+ console.log(` Amount: ${ethers_1.ethers.formatUnits(kiteOption.maxAmountRequired, 18)} TestUSD`);
90
+ console.log(` Pay to: ${kiteOption.payTo}`);
91
+ return kiteOption;
92
+ }
93
+ else {
94
+ console.log(" ❌ No kite-testnet payment option available");
95
+ console.log(" Available networks:", data.accepts?.map((r) => r.network).join(", "));
96
+ return null;
97
+ }
98
+ }
99
+ else if (response.ok) {
100
+ console.log(" ⚠️ Content returned without payment (unexpected)");
101
+ const content = await response.text();
102
+ console.log(" Content:", content.slice(0, 100));
103
+ return null;
104
+ }
105
+ else {
106
+ console.log(` ❌ Unexpected status: ${response.status}`);
107
+ return null;
108
+ }
109
+ }
110
+ /**
111
+ * Step 2: Sign payment authorization
112
+ */
113
+ async function signPayment(requirements) {
114
+ console.log("\n Step 2: Sign Payment Authorization");
115
+ console.log("─".repeat(50));
116
+ const nonce = ethers_1.ethers.hexlify((0, ethers_1.randomBytes)(32));
117
+ const now = BigInt(Math.floor(Date.now() / 1000));
118
+ const auth = {
119
+ from: AA_WALLET,
120
+ to: requirements.payTo,
121
+ token: requirements.asset,
122
+ value: BigInt(requirements.maxAmountRequired),
123
+ validAfter: now - 60n,
124
+ validBefore: now + 3600n,
125
+ nonce,
126
+ };
127
+ console.log(` From: ${auth.from}`);
128
+ console.log(` To: ${auth.to}`);
129
+ console.log(` Token: ${auth.token}`);
130
+ console.log(` Value: ${ethers_1.ethers.formatUnits(auth.value, 18)} TestUSD`);
131
+ console.log(` Nonce: ${nonce.slice(0, 18)}...`);
132
+ const signature = await signTransferAuthorization(AA_WALLET, CHAIN_ID, auth);
133
+ console.log(` Signature: ${signature.slice(0, 20)}...${signature.slice(-8)}`);
134
+ console.log(" ✅ Authorization signed");
135
+ const paymentPayload = {
136
+ x402Version: 1,
137
+ scheme: "gokite-aa",
138
+ network: requirements.network,
139
+ payload: {
140
+ signature,
141
+ authorization: {
142
+ from: auth.from,
143
+ to: auth.to,
144
+ token: auth.token,
145
+ value: auth.value.toString(),
146
+ validAfter: auth.validAfter.toString(),
147
+ validBefore: auth.validBefore.toString(),
148
+ nonce: auth.nonce,
149
+ },
150
+ sessionId: SESSION_ID,
151
+ },
152
+ };
153
+ return { paymentPayload, nonce };
154
+ }
155
+ /**
156
+ * Step 3: Send payment and get content
157
+ */
158
+ async function buyContent(endpoint, paymentPayload) {
159
+ console.log("\n Step 3: Send Payment & Get Content");
160
+ console.log("─".repeat(50));
161
+ // Encode payment payload as base64
162
+ const xPayment = Buffer.from(JSON.stringify(paymentPayload)).toString("base64");
163
+ console.log(` X-Payment header length: ${xPayment.length} chars`);
164
+ const response = await fetch(`${SERVICE_URL}${endpoint}`, {
165
+ headers: {
166
+ "X-Payment": xPayment,
167
+ },
168
+ });
169
+ console.log(` Status: ${response.status}`);
170
+ if (response.ok) {
171
+ const contentType = response.headers.get("content-type");
172
+ let content;
173
+ if (contentType?.includes("application/json")) {
174
+ const json = await response.json();
175
+ content = JSON.stringify(json, null, 2);
176
+ }
177
+ else {
178
+ content = await response.text();
179
+ }
180
+ console.log(" ✅ Content received!");
181
+ return { success: true, content };
182
+ }
183
+ else {
184
+ const error = await response.text();
185
+ console.log(" ❌ Payment failed");
186
+ console.log(" Error:", error);
187
+ return { success: false, error };
188
+ }
189
+ }
190
+ // =============================================================================
191
+ // Health Checks
192
+ // =============================================================================
193
+ async function checkFacilitator() {
194
+ console.log("\n🏥 Facilitator Health Check");
195
+ console.log("─".repeat(50));
196
+ console.log(` URL: ${FACILITATOR_URL}`);
197
+ try {
198
+ const response = await fetch(`${FACILITATOR_URL}/health`);
199
+ if (response.ok) {
200
+ console.log(" ✅ Facilitator is healthy");
201
+ return true;
202
+ }
203
+ console.log(` ❌ Status: ${response.status}`);
204
+ return false;
205
+ }
206
+ catch (error) {
207
+ console.log(" ❌ Connection failed:", error.message);
208
+ return false;
209
+ }
210
+ }
211
+ async function checkService() {
212
+ console.log("\n🏥 Service Health Check");
213
+ console.log("─".repeat(50));
214
+ console.log(` URL: ${SERVICE_URL}`);
215
+ try {
216
+ const response = await fetch(`${SERVICE_URL}/health`);
217
+ if (response.ok) {
218
+ console.log(" ✅ Service is healthy");
219
+ return true;
220
+ }
221
+ console.log(` ❌ Status: ${response.status}`);
222
+ return false;
223
+ }
224
+ catch (error) {
225
+ console.log(" ❌ Connection failed:", error.message);
226
+ return false;
227
+ }
228
+ }
229
+ // =============================================================================
230
+ // Main Flow
231
+ // =============================================================================
232
+ async function main() {
233
+ const endpoint = process.argv[2] || "/protected-route";
234
+ console.log("═".repeat(60));
235
+ console.log(" x402 Gokite Payment Demo - Client");
236
+ console.log("═".repeat(60));
237
+ console.log("\n📍 Configuration");
238
+ console.log("─".repeat(50));
239
+ console.log(` AA Wallet: ${AA_WALLET}`);
240
+ console.log(` Session ID: ${SESSION_ID.slice(0, 18)}...`);
241
+ console.log(` Agent: ${agentSigner.address}`);
242
+ console.log(` Service: ${SERVICE_URL}`);
243
+ console.log(` Endpoint: ${endpoint}`);
244
+ // // Health checks
245
+ // const facilitatorOk = await checkFacilitator();
246
+ // const serviceOk = await checkService();
247
+ // if (!facilitatorOk || !serviceOk) {
248
+ // console.error("\n❌ Health checks failed. Please ensure services are running:");
249
+ // console.error(" 1. Facilitator: cargo run (port 8080)");
250
+ // console.error(" 2. Service: cd examples/x402-axum-example && cargo run (port 8099)");
251
+ // process.exit(1);
252
+ // }
253
+ // Step 1: Request content (expect 402)
254
+ const requirements = await requestContent(endpoint);
255
+ if (!requirements) {
256
+ console.error("\n❌ Could not get payment requirements");
257
+ process.exit(1);
258
+ }
259
+ // Step 2: Sign payment
260
+ const { paymentPayload, nonce } = await signPayment(requirements);
261
+ // Step 3: Send payment and get content
262
+ const result = await buyContent(endpoint, paymentPayload);
263
+ // Summary
264
+ console.log("\n" + "═".repeat(60));
265
+ console.log(" Result");
266
+ console.log("═".repeat(60));
267
+ if (result.success) {
268
+ console.log(" ✅ Purchase successful!");
269
+ console.log("\n Content:");
270
+ console.log(" " + "─".repeat(48));
271
+ console.log(result.content?.split("\n").map((l) => " " + l).join("\n"));
272
+ console.log(" " + "─".repeat(48));
273
+ console.log(`\n Payment nonce: ${nonce}`);
274
+ console.log(" (Nonce is now used - replay protection active)");
275
+ }
276
+ else {
277
+ console.log(" ❌ Purchase failed");
278
+ console.log(` Error: ${result.error}`);
279
+ }
280
+ console.log("═".repeat(60));
281
+ }
282
+ main().catch((error) => {
283
+ console.error(error);
284
+ process.exit(1);
285
+ });
package/dist/config.js CHANGED
@@ -6,7 +6,7 @@ exports.NETWORKS = {
6
6
  chainId: 2368,
7
7
  entryPoint: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108',
8
8
  accountFactory: '0xAba80c4c8748c114Ba8b61cda3b0112333C3b96E',
9
- accountImplementation: '0xc033eC55a6c6fa7aDe6EcE95835113fC64Fc2e3F',
9
+ accountImplementation: '0x376c32FcE28aa8496465a9E5C8a03ED8B1a7fB4D',
10
10
  paymaster: '0x9Adcbf85D5c724611a490Ba9eDc4d38d6F39e92d',
11
11
  supportedTokens: [
12
12
  { address: '0x0000000000000000000000000000000000000000', symbol: 'KITE', decimals: 18 },
@@ -1,127 +1,493 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * GokiteAccount E2E Demo with x402 Facilitator Integration
5
+ *
6
+ * This script demonstrates the complete AA wallet payment flow:
7
+ * 1. Setup AA wallet: addSupportedToken + setMasterBudgetRules
8
+ * 2. Create session with agent and spending rules
9
+ * 3. Agent signs EIP-712 transfer authorization
10
+ * 4. Verify payment via x402 facilitator
11
+ * 5. Settle payment via x402 facilitator (or direct UserOp execution)
12
+ *
13
+ * Usage:
14
+ * npx tsx example-token-paymaster.ts [--settle] [--direct]
15
+ *
16
+ * Options:
17
+ * --settle Execute real settlement via facilitator
18
+ * --direct Execute direct UserOp instead of facilitator
19
+ */
3
20
  const ethers_1 = require("ethers");
4
21
  const gokite_aa_sdk_1 = require("./gokite-aa-sdk");
5
22
  require("dotenv/config");
23
+ // =============================================================================
24
+ // Configuration
25
+ // =============================================================================
6
26
  const NETWORK = "kite_testnet";
7
27
  const RPC_URL = "https://rpc-testnet.gokite.ai";
8
28
  const BUNDLER_URL = "https://bundler-service.staging.gokite.ai/rpc/";
9
- // const BUNDLER_URL = "http://localhost:14337/rpc/";
10
29
  const SETTLEMENT_TOKEN = "0x0fF5393387ad2f9f691FD6Fd28e07E3969e27e63";
11
30
  const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
31
+ // x402 Facilitator configuration
32
+ const FACILITATOR_URL = process.env.FACILITATOR_URL || "http://localhost:8080";
33
+ const X402_NETWORK = "kite-testnet";
34
+ const CHAIN_ID = 2368;
12
35
  const sdk = new gokite_aa_sdk_1.GokiteAASDK(NETWORK, RPC_URL, BUNDLER_URL);
13
- const sessionInterface = new ethers_1.ethers.Interface([
14
- "function createSession(bytes32 sessionId, address settlementToken, tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders)[] rules)",
36
+ // GokiteAccount interface (includes SessionManager functions)
37
+ const gokiteAccountInterface = new ethers_1.ethers.Interface([
38
+ // Token whitelist
39
+ "function addSupportedToken(address token)",
40
+ // Master budget
41
+ "function setMasterBudgetRules(uint256[] timeWindows, uint160[] budgets)",
42
+ // Session management
43
+ "function createSession(bytes32 sessionId, address agent, tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders)[] rules)",
15
44
  "function setSpendingRules(bytes32 sessionId, tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders)[] rules)",
45
+ // Transfer execution
46
+ "function executeTransferWithAuthorization(bytes32 sessionId, tuple(address from, address to, address token, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce) auth, bytes signature)",
47
+ // Batch execution
48
+ "function executeBatch(address[] dest, uint256[] value, bytes[] func)",
49
+ // ============ View Functions (for frontend queries) ============
50
+ // Token whitelist queries
51
+ "function isTokenSupported(address token) view returns (bool)",
52
+ "function getTokenDecimals(address token) view returns (uint8)",
53
+ "function getAvailableBalance(address token) view returns (uint256)",
54
+ // Session queries
55
+ "function sessionExists(bytes32 sessionId) view returns (bool)",
56
+ "function getSessionAgent(bytes32 sessionId) view returns (address)",
57
+ "function getSpendingRules(bytes32 sessionId) view returns (tuple(tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders) rule, tuple(uint128 amountUsed, uint128 currentTimeWindowStartTime) usage)[])",
58
+ "function getUsage(bytes32 sessionId, uint256 index) view returns (uint256)",
59
+ "function checkSpendingRules(bytes32 sessionId, uint256 normalizedAmount, bytes32 serviceProvider) view returns (bool)",
60
+ // Master budget queries
61
+ "function getMasterBudgetRules() view returns (tuple(tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders) rule, tuple(uint128 amountUsed, uint128 currentTimeWindowStartTime) usage)[])",
62
+ "function getMasterBudgetRuleCount() view returns (uint256)",
63
+ // Nonce queries
64
+ "function isNonceUsed(bytes32 nonce) view returns (bool)",
16
65
  ]);
17
- const erc20Interface = new ethers_1.ethers.Interface([
18
- "function transfer(address to, uint256 amount)",
19
- ]);
20
- /**
21
- * Frontend note:
22
- * 1. Creates an AA session and seeds it with example spending rules (daily, weekly, per-transaction caps).
23
- * 2. Updates that session to demonstrate how rule changes propagate on-chain.
24
- * 3. Executes an ERC20 transfer
25
- */
26
- const PRIVATE_KEY = process.env.PRIVATE_KEY;
66
+ // =============================================================================
67
+ // Environment Setup
68
+ // =============================================================================
69
+ // Owner: controls AA wallet, signs UserOps
70
+ const OWNER_PRIVATE_KEY = process.env.PRIVATE_KEY;
71
+ // Agent: signs transfer authorizations (can be same as owner for demo)
72
+ const AGENT_PRIVATE_KEY = process.env.AGENT_PRIVATE_KEY ?? OWNER_PRIVATE_KEY;
27
73
  const PAYMENT_TOKEN = process.env.PAYMENT_TOKEN ?? SETTLEMENT_TOKEN;
28
- if (!PRIVATE_KEY)
74
+ if (!OWNER_PRIVATE_KEY)
29
75
  throw new Error("Missing PRIVATE_KEY env variable");
30
- const signer = new ethers_1.ethers.Wallet(PRIVATE_KEY);
31
- const sessionId = (0, ethers_1.randomBytes)(32);
32
- const secondsPerDay = 86400;
76
+ const ownerSigner = new ethers_1.ethers.Wallet(OWNER_PRIVATE_KEY);
77
+ const agentSigner = new ethers_1.ethers.Wallet(AGENT_PRIVATE_KEY);
78
+ const sessionId = ethers_1.ethers.hexlify((0, ethers_1.randomBytes)(32));
79
+ const SECONDS_PER_DAY = 86400;
33
80
  function currentDayStart() {
34
81
  const now = Math.floor(Date.now() / 1000);
35
- return BigInt(Math.floor(now / secondsPerDay) * secondsPerDay);
36
- }
37
- function currentWeekStart() {
38
- const now = new Date();
39
- const dayStart = Number(currentDayStart());
40
- const weekday = now.getUTCDay();
41
- const daysFromMonday = (weekday + 6) % 7;
42
- return BigInt(dayStart - daysFromMonday * secondsPerDay);
82
+ return BigInt(Math.floor(now / SECONDS_PER_DAY) * SECONDS_PER_DAY);
43
83
  }
44
84
  async function signUserOp(hash) {
45
- return signer.signMessage(ethers_1.ethers.getBytes(hash));
85
+ return ownerSigner.signMessage(ethers_1.ethers.getBytes(hash));
46
86
  }
47
87
  async function sendWithTokenPayment(target, callData) {
48
- const request = {
49
- target,
50
- value: 0n,
51
- callData,
52
- };
53
- const estimate = await sdk.estimateUserOperation(signer.address, request);
88
+ const request = { target, value: 0n, callData };
89
+ const estimate = await sdk.estimateUserOperation(ownerSigner.address, request);
54
90
  const tokenAddress = estimate.sponsorshipAvailable ? ZERO_ADDRESS : PAYMENT_TOKEN;
55
- const response = await sdk.sendUserOperationWithPayment(signer.address, request, estimate.userOp, tokenAddress, signUserOp);
91
+ const response = await sdk.sendUserOperationWithPayment(ownerSigner.address, request, estimate.userOp, tokenAddress, signUserOp);
56
92
  if (response.status.status !== "success") {
57
93
  throw new Error(response.status.reason ?? "User operation failed");
58
94
  }
59
95
  return response.status.transactionHash ?? "";
60
96
  }
61
- function initialRules() {
97
+ // EIP-712 domain for GokiteAccount
98
+ function getEIP712Domain(aaWallet, chainId) {
99
+ return {
100
+ name: "GokiteAccount",
101
+ version: "1",
102
+ chainId: chainId,
103
+ verifyingContract: aaWallet,
104
+ };
105
+ }
106
+ // EIP-712 types for TransferWithAuthorization
107
+ const TRANSFER_AUTH_TYPES = {
108
+ TransferWithAuthorization: [
109
+ { name: "from", type: "address" },
110
+ { name: "to", type: "address" },
111
+ { name: "token", type: "address" },
112
+ { name: "value", type: "uint256" },
113
+ { name: "validAfter", type: "uint256" },
114
+ { name: "validBefore", type: "uint256" },
115
+ { name: "nonce", type: "bytes32" },
116
+ ],
117
+ };
118
+ // Sign transfer authorization using EIP-712
119
+ async function signTransferAuthorization(aaWallet, chainId, auth) {
120
+ const domain = getEIP712Domain(aaWallet, chainId);
121
+ return agentSigner.signTypedData(domain, TRANSFER_AUTH_TYPES, auth);
122
+ }
123
+ // Session spending rules
124
+ function sessionRules() {
62
125
  return [
63
126
  {
64
- timeWindow: 86400n,
127
+ timeWindow: BigInt(SECONDS_PER_DAY),
65
128
  budget: ethers_1.ethers.parseUnits("100", 18),
66
129
  initialWindowStartTime: currentDayStart(),
67
130
  targetProviders: [],
68
131
  },
69
- {
70
- timeWindow: 604800n,
71
- budget: ethers_1.ethers.parseUnits("1000", 18),
72
- initialWindowStartTime: currentWeekStart(),
73
- targetProviders: [],
74
- },
75
132
  {
76
133
  timeWindow: 0n,
77
134
  budget: ethers_1.ethers.parseUnits("10", 18),
78
135
  initialWindowStartTime: 0n,
79
- targetProviders: [
80
- ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes("provider1")),
81
- ],
136
+ targetProviders: [],
82
137
  },
83
138
  ];
84
139
  }
85
- function updatedRules() {
86
- return [
140
+ // =============================================================================
141
+ // x402 Facilitator API Functions
142
+ // =============================================================================
143
+ async function facilitatorHealthCheck() {
144
+ console.log("\n[x402] Health Check");
145
+ console.log("=".repeat(50));
146
+ try {
147
+ const response = await fetch(`${FACILITATOR_URL}/health`);
148
+ const data = await response.json();
149
+ console.log(" Response:", JSON.stringify(data, null, 2));
150
+ console.log(" ✅ Facilitator is healthy!");
151
+ return true;
152
+ }
153
+ catch (error) {
154
+ console.error(" ❌ Health check failed:", error);
155
+ return false;
156
+ }
157
+ }
158
+ async function getSupportedNetworks() {
159
+ console.log("\n[x402] Supported Networks");
160
+ console.log("=".repeat(50));
161
+ try {
162
+ const response = await fetch(`${FACILITATOR_URL}/supported`);
163
+ const data = (await response.json());
164
+ console.log(" Response:", JSON.stringify(data, null, 2));
165
+ const kinds = data.kinds || [];
166
+ const hasKite = kinds.some((k) => k.network === X402_NETWORK);
167
+ if (hasKite) {
168
+ console.log(" ✅ Kite testnet is supported!");
169
+ return true;
170
+ }
171
+ else {
172
+ console.log(" ❌ Kite testnet NOT found in supported networks");
173
+ return false;
174
+ }
175
+ }
176
+ catch (error) {
177
+ console.error(" ❌ Failed to get supported networks:", error);
178
+ return false;
179
+ }
180
+ }
181
+ function buildVerifyRequest(authorization, signature, sessionIdHex, recipient) {
182
+ return {
183
+ x402Version: 1,
184
+ paymentPayload: {
185
+ x402Version: 1,
186
+ scheme: "gokite-aa",
187
+ network: X402_NETWORK,
188
+ payload: {
189
+ signature,
190
+ authorization,
191
+ sessionId: sessionIdHex,
192
+ },
193
+ },
194
+ paymentRequirements: {
195
+ scheme: "gokite-aa",
196
+ network: X402_NETWORK,
197
+ maxAmountRequired: authorization.value,
198
+ resource: "https://example.com/api",
199
+ description: "Test payment via AA wallet",
200
+ mimeType: "application/json",
201
+ payTo: recipient,
202
+ maxTimeoutSeconds: 30,
203
+ asset: authorization.token,
204
+ },
205
+ };
206
+ }
207
+ async function verifyPayment(request) {
208
+ console.log("\n[x402] Verify Payment");
209
+ console.log("=".repeat(50));
210
+ console.log(" Request:", JSON.stringify(request, null, 2));
211
+ try {
212
+ const response = await fetch(`${FACILITATOR_URL}/verify`, {
213
+ method: "POST",
214
+ headers: { "Content-Type": "application/json" },
215
+ body: JSON.stringify(request),
216
+ });
217
+ const data = (await response.json());
218
+ console.log("\n Response:", JSON.stringify(data, null, 2));
219
+ if (data.isValid) {
220
+ console.log("\n ✅ Verification PASSED!");
221
+ }
222
+ else {
223
+ console.log("\n ❌ Verification FAILED:", data.invalidReason);
224
+ }
225
+ return data;
226
+ }
227
+ catch (error) {
228
+ console.error(" ❌ Verify request failed:", error);
229
+ return null;
230
+ }
231
+ }
232
+ async function settlePayment(request) {
233
+ console.log("\n[x402] Settle Payment");
234
+ console.log("=".repeat(50));
235
+ console.log(" Request:", JSON.stringify(request, null, 2));
236
+ try {
237
+ const response = await fetch(`${FACILITATOR_URL}/settle`, {
238
+ method: "POST",
239
+ headers: { "Content-Type": "application/json" },
240
+ body: JSON.stringify(request),
241
+ });
242
+ const data = (await response.json());
243
+ console.log("\n Response:", JSON.stringify(data, null, 2));
244
+ if (data.success) {
245
+ console.log("\n ✅ Settlement SUCCESSFUL!");
246
+ if (data.transaction) {
247
+ console.log(" Transaction Hash:", data.transaction);
248
+ }
249
+ }
250
+ else {
251
+ console.log("\n ❌ Settlement FAILED:", data.errorReason);
252
+ }
253
+ return data;
254
+ }
255
+ catch (error) {
256
+ console.error(" ❌ Settle request failed:", error);
257
+ return null;
258
+ }
259
+ }
260
+ // =============================================================================
261
+ // Query Functions
262
+ // =============================================================================
263
+ async function queryWalletStatus(aaWallet, provider) {
264
+ console.log("\n[Query] AA Wallet Status");
265
+ console.log("=".repeat(50));
266
+ // Check if wallet is deployed first
267
+ const code = await provider.getCode(aaWallet);
268
+ if (code === "0x") {
269
+ console.log(` AA Wallet ${aaWallet} is not deployed yet (counterfactual address)`);
270
+ console.log(` Wallet will be deployed on first UserOp execution`);
271
+ return { isDeployed: false, isSupported: false, balance: 0n };
272
+ }
273
+ const contract = new ethers_1.ethers.Contract(aaWallet, gokiteAccountInterface, provider);
274
+ // 1. Check if token is supported
275
+ const isSupported = await contract.isTokenSupported(SETTLEMENT_TOKEN);
276
+ console.log(` Token ${SETTLEMENT_TOKEN} supported: ${isSupported}`);
277
+ // 2. Get token balance in AA wallet
278
+ const balance = await contract.getAvailableBalance(SETTLEMENT_TOKEN);
279
+ console.log(` Token balance: ${ethers_1.ethers.formatUnits(balance, 18)}`);
280
+ // 3. Get master budget rules
281
+ const masterRules = await contract.getMasterBudgetRules();
282
+ console.log(` Master budget rules count: ${masterRules.length}`);
283
+ for (let i = 0; i < masterRules.length; i++) {
284
+ const rule = masterRules[i];
285
+ const timeWindow = rule.rule.timeWindow;
286
+ const budget = ethers_1.ethers.formatUnits(rule.rule.budget, 18);
287
+ const used = ethers_1.ethers.formatUnits(rule.usage.amountUsed, 18);
288
+ const windowType = timeWindow === 0n ? "per-tx" : `${Number(timeWindow) / 86400} day(s)`;
289
+ console.log(` Rule ${i}: ${windowType}, budget: ${budget}, used: ${used}`);
290
+ }
291
+ return { isDeployed: true, isSupported, balance };
292
+ }
293
+ async function querySessionStatus(aaWallet, sessionIdHex, provider) {
294
+ const contract = new ethers_1.ethers.Contract(aaWallet, gokiteAccountInterface, provider);
295
+ console.log("\n[Query] Session Status");
296
+ console.log("=".repeat(50));
297
+ // 1. Check if session exists
298
+ const exists = await contract.sessionExists(sessionIdHex);
299
+ console.log(` Session ${sessionIdHex.slice(0, 18)}... exists: ${exists}`);
300
+ if (!exists)
301
+ return { exists };
302
+ // 2. Get session agent
303
+ const agent = await contract.getSessionAgent(sessionIdHex);
304
+ console.log(` Session agent: ${agent}`);
305
+ // 3. Get spending rules
306
+ const spendingRules = await contract.getSpendingRules(sessionIdHex);
307
+ console.log(` Spending rules count: ${spendingRules.length}`);
308
+ for (let i = 0; i < spendingRules.length; i++) {
309
+ const rule = spendingRules[i];
310
+ const timeWindow = rule.rule.timeWindow;
311
+ const budget = ethers_1.ethers.formatUnits(rule.rule.budget, 18);
312
+ const used = ethers_1.ethers.formatUnits(rule.usage.amountUsed, 18);
313
+ const windowType = timeWindow === 0n ? "per-tx" : `${Number(timeWindow) / 86400} day(s)`;
314
+ console.log(` Rule ${i}: ${windowType}, budget: ${budget}, used: ${used}`);
315
+ }
316
+ return { exists, agent, spendingRules };
317
+ }
318
+ async function checkNonceStatus(aaWallet, nonce, provider) {
319
+ const contract = new ethers_1.ethers.Contract(aaWallet, gokiteAccountInterface, provider);
320
+ const isUsed = await contract.isNonceUsed(nonce);
321
+ console.log(` Nonce ${nonce.slice(0, 18)}... used: ${isUsed}`);
322
+ return isUsed;
323
+ }
324
+ // =============================================================================
325
+ // Main Flow
326
+ // =============================================================================
327
+ async function main() {
328
+ const args = process.argv.slice(2);
329
+ const shouldSettle = args.includes("--settle");
330
+ const useDirect = args.includes("--direct");
331
+ console.log("=".repeat(60));
332
+ console.log("GokiteAccount E2E Demo with x402 Facilitator");
333
+ console.log("=".repeat(60));
334
+ const aaWallet = sdk.getAccountAddress(ownerSigner.address);
335
+ const provider = new ethers_1.ethers.JsonRpcProvider(RPC_URL);
336
+ const chainId = (await provider.getNetwork()).chainId;
337
+ console.log("\nConfiguration:");
338
+ console.log(" Facilitator URL:", FACILITATOR_URL);
339
+ console.log(" Network:", X402_NETWORK);
340
+ console.log(" Chain ID:", CHAIN_ID);
341
+ console.log(" AA Wallet:", aaWallet);
342
+ console.log(" Owner:", ownerSigner.address);
343
+ console.log(" Agent:", agentSigner.address);
344
+ console.log(" Token:", SETTLEMENT_TOKEN);
345
+ console.log(" Mode:", useDirect ? "Direct UserOp" : "x402 Facilitator");
346
+ // ============ Query Wallet Status ============
347
+ const walletStatus = await queryWalletStatus(aaWallet, provider);
348
+ // ============ Step 1: Setup AA Wallet (only if not deployed) ============
349
+ if (!walletStatus.isDeployed) {
350
+ console.log("\n[Step 1] Setting up AA wallet...");
351
+ const masterBudgetData = gokiteAccountInterface.encodeFunctionData("setMasterBudgetRules", [
352
+ [BigInt(SECONDS_PER_DAY), 0n],
353
+ [ethers_1.ethers.parseUnits("1000", 18), ethers_1.ethers.parseUnits("100", 18)], // 1000 USD/day, 100 USD/tx
354
+ ]);
355
+ const setupBatchData = gokiteAccountInterface.encodeFunctionData("executeBatch", [
356
+ [aaWallet],
357
+ [],
358
+ [masterBudgetData],
359
+ ]);
360
+ const setupTx = await sendWithTokenPayment(aaWallet, setupBatchData);
361
+ console.log(` Setup (addSupportedToken + setMasterBudgetRules) tx: ${setupTx}`);
362
+ await new Promise((r) => setTimeout(r, 5000));
363
+ }
364
+ else {
365
+ console.log("\n[Step 1] AA wallet already deployed, skipping setup");
366
+ }
367
+ // ============ Step 2: Create Session ============
368
+ console.log("\n[Step 2] Creating session...");
369
+ const createSessionData = gokiteAccountInterface.encodeFunctionData("createSession", [
370
+ sessionId,
371
+ agentSigner.address,
372
+ sessionRules(),
373
+ ]);
374
+ const createSessionTx = await sendWithTokenPayment(aaWallet, createSessionData);
375
+ console.log(` Session ID: ${sessionId}`);
376
+ console.log(` createSession tx: ${createSessionTx}`);
377
+ await new Promise((r) => setTimeout(r, 5000));
378
+ // Query session status after creation
379
+ await querySessionStatus(aaWallet, sessionId, provider);
380
+ // ============ Step 2b: Update Spending Rules (setSpendingRules example) ============
381
+ console.log("\n[Step 2b] Updating session spending rules...");
382
+ // Define new spending rules to replace existing ones
383
+ const newSpendingRules = [
87
384
  {
88
- timeWindow: 86400n,
385
+ timeWindow: BigInt(SECONDS_PER_DAY),
89
386
  budget: ethers_1.ethers.parseUnits("200", 18),
90
387
  initialWindowStartTime: currentDayStart(),
91
388
  targetProviders: [],
92
389
  },
93
390
  {
94
391
  timeWindow: 0n,
95
- budget: ethers_1.ethers.parseUnits("5", 18),
392
+ budget: ethers_1.ethers.parseUnits("20", 18),
96
393
  initialWindowStartTime: 0n,
97
394
  targetProviders: [],
98
395
  },
396
+ {
397
+ timeWindow: BigInt(SECONDS_PER_DAY * 7),
398
+ budget: ethers_1.ethers.parseUnits("500", 18),
399
+ initialWindowStartTime: currentDayStart(),
400
+ targetProviders: [],
401
+ },
99
402
  ];
100
- }
101
- async function main() {
102
- const aaWallet = sdk.getAccountAddress(signer.address);
103
- const createSessionData = sessionInterface.encodeFunctionData("createSession", [
403
+ const setSpendingRulesData = gokiteAccountInterface.encodeFunctionData("setSpendingRules", [
104
404
  sessionId,
105
- SETTLEMENT_TOKEN,
106
- initialRules(),
107
- ]);
108
- const createTx = await sendWithTokenPayment(aaWallet, createSessionData);
109
- console.log(`createSession tx: ${createTx}`);
110
- await new Promise(resolve => setTimeout(resolve, 10000));
111
- const updateRulesData = sessionInterface.encodeFunctionData("setSpendingRules", [
112
- sessionId,
113
- updatedRules(),
114
- ]);
115
- const updateTx = await sendWithTokenPayment(aaWallet, updateRulesData);
116
- console.log(`setSpendingRules tx: ${updateTx}`);
117
- await new Promise(resolve => setTimeout(resolve, 10000));
118
- const RECIPIENT = signer.address;
119
- const transferData = erc20Interface.encodeFunctionData("transfer", [
120
- RECIPIENT,
121
- ethers_1.ethers.parseUnits("0", 18),
405
+ newSpendingRules,
122
406
  ]);
123
- const transferTx = await sendWithTokenPayment(SETTLEMENT_TOKEN, transferData);
124
- console.log(`transfer tx: ${transferTx}`);
407
+ const setSpendingRulesTx = await sendWithTokenPayment(aaWallet, setSpendingRulesData);
408
+ console.log(` setSpendingRules tx: ${setSpendingRulesTx}`);
409
+ await new Promise((r) => setTimeout(r, 5000));
410
+ // Query session status after updating spending rules
411
+ await querySessionStatus(aaWallet, sessionId, provider);
412
+ // // ============ Step 3: Sign Transfer Authorization ============
413
+ // console.log("\n[Step 3] Agent signing transfer authorization...");
414
+ // const recipient = ownerSigner.address; // send to self for demo
415
+ // const transferAmount = ethers.parseUnits("1", 18); // 1 token
416
+ // const nonce = ethers.hexlify(randomBytes(32));
417
+ // const now = BigInt(Math.floor(Date.now() / 1000));
418
+ // const auth = {
419
+ // from: aaWallet,
420
+ // to: recipient,
421
+ // token: SETTLEMENT_TOKEN,
422
+ // value: transferAmount,
423
+ // validAfter: now - 60n, // valid from 1 minute ago
424
+ // validBefore: now + 3600n, // valid for 1 hour
425
+ // nonce: nonce,
426
+ // };
427
+ // const signature = await signTransferAuthorization(aaWallet, chainId, auth);
428
+ // console.log(` Authorization nonce: ${nonce}`);
429
+ // console.log(` Signature: ${signature.slice(0, 42)}...`);
430
+ // // ============ Step 4: Execute Transfer ============
431
+ // if (useDirect) {
432
+ // // Direct UserOp execution
433
+ // console.log("\n[Step 4] Executing transfer via direct UserOp...");
434
+ // const executeTransferData = gokiteAccountInterface.encodeFunctionData(
435
+ // "executeTransferWithAuthorization",
436
+ // [sessionId, auth, signature]
437
+ // );
438
+ // const executeTx = await sendWithTokenPayment(aaWallet, executeTransferData);
439
+ // console.log(` executeTransferWithAuthorization tx: ${executeTx}`);
440
+ // } else {
441
+ // // x402 Facilitator flow
442
+ // console.log("\n[Step 4] Verifying via x402 facilitator...");
443
+ // // Check facilitator health
444
+ // const healthy = await facilitatorHealthCheck();
445
+ // if (!healthy) {
446
+ // console.error("\n❌ Facilitator not available, aborting");
447
+ // process.exit(1);
448
+ // }
449
+ // // Check supported networks
450
+ // await getSupportedNetworks();
451
+ // // Build x402 request
452
+ // const x402Auth: Authorization = {
453
+ // from: aaWallet,
454
+ // to: recipient,
455
+ // token: SETTLEMENT_TOKEN,
456
+ // value: transferAmount.toString(),
457
+ // validAfter: auth.validAfter.toString(),
458
+ // validBefore: auth.validBefore.toString(),
459
+ // nonce: nonce,
460
+ // };
461
+ // const verifyRequest = buildVerifyRequest(x402Auth, signature, sessionId, recipient);
462
+ // // Verify payment
463
+ // const verifyResult = await verifyPayment(verifyRequest);
464
+ // if (!verifyResult?.isValid) {
465
+ // console.error("\n❌ Verification failed, skipping settle");
466
+ // process.exit(1);
467
+ // }
468
+ // // Settle payment (optional)
469
+ // if (shouldSettle) {
470
+ // console.log("\n⚠️ Proceeding with REAL settlement transaction...");
471
+ // const settleResult = await settlePayment(verifyRequest);
472
+ // if (settleResult?.success && settleResult.transaction) {
473
+ // console.log(` Settlement tx: ${settleResult.transaction}`);
474
+ // }
475
+ // } else {
476
+ // console.log("\n💡 Skipping settle (use --settle flag to execute real transaction)");
477
+ // }
478
+ // }
479
+ // await new Promise((r) => setTimeout(r, 5000));
480
+ // // ============ Post-Transfer Queries ============
481
+ // console.log("\n[Post-Transfer] Checking status...");
482
+ // // Check nonce is now used (replay protection)
483
+ // await checkNonceStatus(aaWallet, nonce, provider);
484
+ // // Query updated session status (usage should be updated)
485
+ // await querySessionStatus(aaWallet, sessionId, provider);
486
+ // // Query updated wallet status
487
+ // await queryWalletStatus(aaWallet, provider);
488
+ // console.log("\n" + "=".repeat(60));
489
+ // console.log("E2E Demo completed successfully!");
490
+ // console.log("=".repeat(60));
125
491
  }
126
492
  main().catch((error) => {
127
493
  console.error(error);
@@ -35,8 +35,13 @@ export declare class GokiteAASDK {
35
35
  * Build call data for batch transactions
36
36
  */
37
37
  buildBatchCallData(request: BatchUserOperationRequest): string;
38
+ /**
39
+ * Prepend addSupportedToken call to request for first transaction (wallet deployment)
40
+ */
41
+ private prependAddSupportedToken;
38
42
  /**
39
43
  * Create user operation
44
+ * If wallet is not deployed, automatically batch addSupportedToken into the first transaction
40
45
  */
41
46
  createUserOperation(owner: string, request: UserOperationRequest | BatchUserOperationRequest, salt?: bigint, paymasterAddress?: string, tokenAddress?: string): Promise<Partial<UserOperation>>;
42
47
  /**
@@ -251,16 +251,44 @@ class GokiteAASDK {
251
251
  const values = request.values || new Array(request.targets.length).fill(0n);
252
252
  return (0, utils_1.encodeFunctionCall)(['function executeBatch(address[],uint256[],bytes[])'], 'executeBatch', [request.targets, values, request.callDatas]);
253
253
  }
254
+ /**
255
+ * Prepend addSupportedToken call to request for first transaction (wallet deployment)
256
+ */
257
+ prependAddSupportedToken(request, accountAddress, tokenAddress) {
258
+ const addTokenCallData = (0, utils_1.encodeFunctionCall)(['function addSupportedToken(address)'], 'addSupportedToken', [tokenAddress]);
259
+ if ('target' in request) {
260
+ // Convert single request to batch with addSupportedToken prepended
261
+ return {
262
+ targets: [accountAddress, request.target],
263
+ values: [BigInt(0), request.value || BigInt(0)],
264
+ callDatas: [addTokenCallData, request.callData]
265
+ };
266
+ }
267
+ else {
268
+ // Prepend to existing batch
269
+ return {
270
+ targets: [accountAddress, ...request.targets],
271
+ values: [BigInt(0), ...(request.values || new Array(request.targets.length).fill(BigInt(0)))],
272
+ callDatas: [addTokenCallData, ...request.callDatas]
273
+ };
274
+ }
275
+ }
254
276
  /**
255
277
  * Create user operation
278
+ * If wallet is not deployed, automatically batch addSupportedToken into the first transaction
256
279
  */
257
280
  async createUserOperation(owner, request, salt, paymasterAddress, tokenAddress) {
258
281
  const actualSalt = salt || (0, utils_1.generateSalt)();
259
282
  const accountAddress = this.getAccountAddress(owner, actualSalt);
260
283
  const isDeployed = await this.isAccountDeloyed(accountAddress);
261
- const callData = 'targets' in request
262
- ? this.buildBatchCallData(request)
263
- : this.buildCallData(request);
284
+ // If wallet not deployed, batch addSupportedToken into first transaction
285
+ let finalRequest = request;
286
+ if (!isDeployed && tokenAddress && tokenAddress !== '0x0000000000000000000000000000000000000000') {
287
+ finalRequest = this.prependAddSupportedToken(request, accountAddress, tokenAddress);
288
+ }
289
+ const callData = 'targets' in finalRequest
290
+ ? this.buildBatchCallData(finalRequest)
291
+ : this.buildCallData(finalRequest);
264
292
  // Gas settings (matching Go implementation)
265
293
  const callGasLimit = BigInt(300000);
266
294
  const verificationGasLimit = BigInt(300000);
package/dist/utils.js CHANGED
@@ -130,7 +130,7 @@ function serializeUserOperation(userOp) {
130
130
  exports.serializeUserOperation = serializeUserOperation;
131
131
  // default salt is 0
132
132
  function generateSalt() {
133
- return BigInt(0);
133
+ return BigInt(1);
134
134
  }
135
135
  exports.generateSalt = generateSalt;
136
136
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gokite-aa-sdk",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Simple and clean Account Abstraction SDK for Gokite",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",