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.
- package/dist/buy-content.d.ts +20 -0
- package/dist/buy-content.js +285 -0
- package/dist/config.js +1 -1
- package/dist/example-token-paymaster.js +437 -71
- package/dist/gokite-aa-sdk.d.ts +5 -0
- package/dist/gokite-aa-sdk.js +31 -3
- package/dist/utils.js +1 -1
- package/package.json +1 -1
|
@@ -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: '
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 (!
|
|
74
|
+
if (!OWNER_PRIVATE_KEY)
|
|
29
75
|
throw new Error("Missing PRIVATE_KEY env variable");
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
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 /
|
|
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
|
|
85
|
+
return ownerSigner.signMessage(ethers_1.ethers.getBytes(hash));
|
|
46
86
|
}
|
|
47
87
|
async function sendWithTokenPayment(target, callData) {
|
|
48
|
-
const request = {
|
|
49
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
86
|
-
|
|
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:
|
|
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("
|
|
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
|
-
|
|
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
|
|
124
|
-
console.log(`
|
|
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);
|
package/dist/gokite-aa-sdk.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/gokite-aa-sdk.js
CHANGED
|
@@ -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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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(
|
|
133
|
+
return BigInt(1);
|
|
134
134
|
}
|
|
135
135
|
exports.generateSalt = generateSalt;
|
|
136
136
|
/**
|