gokite-aa-sdk 1.0.9 → 1.0.10
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/config.js +1 -1
- package/dist/example-token-paymaster.js +201 -77
- package/package.json +1 -1
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 },
|
|
@@ -6,122 +6,246 @@ require("dotenv/config");
|
|
|
6
6
|
const NETWORK = "kite_testnet";
|
|
7
7
|
const RPC_URL = "https://rpc-testnet.gokite.ai";
|
|
8
8
|
const BUNDLER_URL = "https://bundler-service.staging.gokite.ai/rpc/";
|
|
9
|
-
// const BUNDLER_URL = "http://localhost:14337/rpc/";
|
|
10
9
|
const SETTLEMENT_TOKEN = "0x0fF5393387ad2f9f691FD6Fd28e07E3969e27e63";
|
|
11
10
|
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
12
11
|
const sdk = new gokite_aa_sdk_1.GokiteAASDK(NETWORK, RPC_URL, BUNDLER_URL);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"function
|
|
12
|
+
// GokiteAccount interface (includes SessionManager functions)
|
|
13
|
+
const gokiteAccountInterface = new ethers_1.ethers.Interface([
|
|
14
|
+
// Token whitelist
|
|
15
|
+
"function addSupportedToken(address token)",
|
|
16
|
+
// Master budget
|
|
17
|
+
"function setMasterBudgetRules(uint256[] timeWindows, uint160[] budgets)",
|
|
18
|
+
// Session management
|
|
19
|
+
"function createSession(bytes32 sessionId, address agent, tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders)[] rules)",
|
|
20
|
+
// Transfer execution
|
|
21
|
+
"function executeTransferWithAuthorization(bytes32 sessionId, tuple(address from, address to, address token, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce) auth, bytes signature)",
|
|
22
|
+
// Batch execution
|
|
23
|
+
"function executeBatch(address[] dest, uint256[] value, bytes[] func)",
|
|
24
|
+
// ============ View Functions (for frontend queries) ============
|
|
25
|
+
// Token whitelist queries
|
|
26
|
+
"function isTokenSupported(address token) view returns (bool)",
|
|
27
|
+
"function getTokenDecimals(address token) view returns (uint8)",
|
|
28
|
+
"function getAvailableBalance(address token) view returns (uint256)",
|
|
29
|
+
// Session queries
|
|
30
|
+
"function sessionExists(bytes32 sessionId) view returns (bool)",
|
|
31
|
+
"function getSessionAgent(bytes32 sessionId) view returns (address)",
|
|
32
|
+
"function getSpendingRules(bytes32 sessionId) view returns (tuple(tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders) rule, tuple(uint128 amountUsed, uint128 currentTimeWindowStartTime) usage)[])",
|
|
33
|
+
"function getUsage(bytes32 sessionId, uint256 index) view returns (uint256)",
|
|
34
|
+
"function checkSpendingRules(bytes32 sessionId, uint256 normalizedAmount, bytes32 serviceProvider) view returns (bool)",
|
|
35
|
+
// Master budget queries
|
|
36
|
+
"function getMasterBudgetRules() view returns (tuple(tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders) rule, tuple(uint128 amountUsed, uint128 currentTimeWindowStartTime) usage)[])",
|
|
37
|
+
"function getMasterBudgetRuleCount() view returns (uint256)",
|
|
38
|
+
// Nonce queries
|
|
39
|
+
"function isNonceUsed(bytes32 nonce) view returns (bool)",
|
|
19
40
|
]);
|
|
20
41
|
/**
|
|
21
|
-
*
|
|
22
|
-
* 1.
|
|
23
|
-
* 2.
|
|
24
|
-
* 3.
|
|
42
|
+
* E2E Demo for AA Session, Budget, and Pay flow:
|
|
43
|
+
* 1. Setup AA wallet: addSupportedToken + setMasterBudgetRules
|
|
44
|
+
* 2. Create session with agent and spending rules
|
|
45
|
+
* 3. Agent signs EIP-712 transfer authorization
|
|
46
|
+
* 4. Execute transfer with authorization
|
|
25
47
|
*/
|
|
26
|
-
|
|
48
|
+
// Owner: controls AA wallet, signs UserOps
|
|
49
|
+
const OWNER_PRIVATE_KEY = process.env.PRIVATE_KEY;
|
|
50
|
+
// Agent: signs transfer authorizations (can be same as owner for demo)
|
|
51
|
+
const AGENT_PRIVATE_KEY = process.env.AGENT_PRIVATE_KEY ?? OWNER_PRIVATE_KEY;
|
|
27
52
|
const PAYMENT_TOKEN = process.env.PAYMENT_TOKEN ?? SETTLEMENT_TOKEN;
|
|
28
|
-
if (!
|
|
53
|
+
if (!OWNER_PRIVATE_KEY)
|
|
29
54
|
throw new Error("Missing PRIVATE_KEY env variable");
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
55
|
+
const ownerSigner = new ethers_1.ethers.Wallet(OWNER_PRIVATE_KEY);
|
|
56
|
+
const agentSigner = new ethers_1.ethers.Wallet(AGENT_PRIVATE_KEY);
|
|
57
|
+
const sessionId = ethers_1.ethers.hexlify((0, ethers_1.randomBytes)(32));
|
|
58
|
+
const SECONDS_PER_DAY = 86400;
|
|
33
59
|
function currentDayStart() {
|
|
34
60
|
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);
|
|
61
|
+
return BigInt(Math.floor(now / SECONDS_PER_DAY) * SECONDS_PER_DAY);
|
|
43
62
|
}
|
|
44
63
|
async function signUserOp(hash) {
|
|
45
|
-
return
|
|
64
|
+
return ownerSigner.signMessage(ethers_1.ethers.getBytes(hash));
|
|
46
65
|
}
|
|
47
66
|
async function sendWithTokenPayment(target, callData) {
|
|
48
|
-
const request = {
|
|
49
|
-
|
|
50
|
-
value: 0n,
|
|
51
|
-
callData,
|
|
52
|
-
};
|
|
53
|
-
const estimate = await sdk.estimateUserOperation(signer.address, request);
|
|
67
|
+
const request = { target, value: 0n, callData };
|
|
68
|
+
const estimate = await sdk.estimateUserOperation(ownerSigner.address, request);
|
|
54
69
|
const tokenAddress = estimate.sponsorshipAvailable ? ZERO_ADDRESS : PAYMENT_TOKEN;
|
|
55
|
-
const response = await sdk.sendUserOperationWithPayment(
|
|
70
|
+
const response = await sdk.sendUserOperationWithPayment(ownerSigner.address, request, estimate.userOp, tokenAddress, signUserOp);
|
|
56
71
|
if (response.status.status !== "success") {
|
|
57
72
|
throw new Error(response.status.reason ?? "User operation failed");
|
|
58
73
|
}
|
|
59
74
|
return response.status.transactionHash ?? "";
|
|
60
75
|
}
|
|
61
|
-
|
|
76
|
+
// EIP-712 domain for GokiteAccount
|
|
77
|
+
function getEIP712Domain(aaWallet, chainId) {
|
|
78
|
+
return {
|
|
79
|
+
name: "GokiteAccount",
|
|
80
|
+
version: "1",
|
|
81
|
+
chainId: chainId,
|
|
82
|
+
verifyingContract: aaWallet,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// EIP-712 types for TransferWithAuthorization
|
|
86
|
+
const TRANSFER_AUTH_TYPES = {
|
|
87
|
+
TransferWithAuthorization: [
|
|
88
|
+
{ name: "from", type: "address" },
|
|
89
|
+
{ name: "to", type: "address" },
|
|
90
|
+
{ name: "token", type: "address" },
|
|
91
|
+
{ name: "value", type: "uint256" },
|
|
92
|
+
{ name: "validAfter", type: "uint256" },
|
|
93
|
+
{ name: "validBefore", type: "uint256" },
|
|
94
|
+
{ name: "nonce", type: "bytes32" },
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
// Sign transfer authorization using EIP-712
|
|
98
|
+
async function signTransferAuthorization(aaWallet, chainId, auth) {
|
|
99
|
+
const domain = getEIP712Domain(aaWallet, chainId);
|
|
100
|
+
return agentSigner.signTypedData(domain, TRANSFER_AUTH_TYPES, auth);
|
|
101
|
+
}
|
|
102
|
+
// Session spending rules
|
|
103
|
+
function sessionRules() {
|
|
62
104
|
return [
|
|
63
105
|
{
|
|
64
|
-
timeWindow:
|
|
106
|
+
timeWindow: BigInt(SECONDS_PER_DAY),
|
|
65
107
|
budget: ethers_1.ethers.parseUnits("100", 18),
|
|
66
108
|
initialWindowStartTime: currentDayStart(),
|
|
67
109
|
targetProviders: [],
|
|
68
110
|
},
|
|
69
|
-
{
|
|
70
|
-
timeWindow: 604800n,
|
|
71
|
-
budget: ethers_1.ethers.parseUnits("1000", 18),
|
|
72
|
-
initialWindowStartTime: currentWeekStart(),
|
|
73
|
-
targetProviders: [],
|
|
74
|
-
},
|
|
75
111
|
{
|
|
76
112
|
timeWindow: 0n,
|
|
77
113
|
budget: ethers_1.ethers.parseUnits("10", 18),
|
|
78
114
|
initialWindowStartTime: 0n,
|
|
79
|
-
targetProviders: [
|
|
80
|
-
ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes("provider1")),
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
|
-
];
|
|
84
|
-
}
|
|
85
|
-
function updatedRules() {
|
|
86
|
-
return [
|
|
87
|
-
{
|
|
88
|
-
timeWindow: 86400n,
|
|
89
|
-
budget: ethers_1.ethers.parseUnits("200", 18),
|
|
90
|
-
initialWindowStartTime: currentDayStart(),
|
|
91
|
-
targetProviders: [],
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
timeWindow: 0n,
|
|
95
|
-
budget: ethers_1.ethers.parseUnits("5", 18),
|
|
96
|
-
initialWindowStartTime: 0n,
|
|
97
115
|
targetProviders: [],
|
|
98
116
|
},
|
|
99
117
|
];
|
|
100
118
|
}
|
|
119
|
+
// ============ Frontend View Functions Examples ============
|
|
120
|
+
async function queryWalletStatus(aaWallet, provider) {
|
|
121
|
+
const contract = new ethers_1.ethers.Contract(aaWallet, gokiteAccountInterface, provider);
|
|
122
|
+
console.log("\n[Query] AA Wallet Status");
|
|
123
|
+
console.log("=".repeat(50));
|
|
124
|
+
// 1. Check if token is supported
|
|
125
|
+
const isSupported = await contract.isTokenSupported(SETTLEMENT_TOKEN);
|
|
126
|
+
console.log(` Token ${SETTLEMENT_TOKEN} supported: ${isSupported}`);
|
|
127
|
+
// 2. Get token balance in AA wallet
|
|
128
|
+
const balance = await contract.getAvailableBalance(SETTLEMENT_TOKEN);
|
|
129
|
+
console.log(` Token balance: ${ethers_1.ethers.formatUnits(balance, 18)}`);
|
|
130
|
+
// 3. Get master budget rules
|
|
131
|
+
const masterRules = await contract.getMasterBudgetRules();
|
|
132
|
+
console.log(` Master budget rules count: ${masterRules.length}`);
|
|
133
|
+
for (let i = 0; i < masterRules.length; i++) {
|
|
134
|
+
const rule = masterRules[i];
|
|
135
|
+
const timeWindow = rule.rule.timeWindow;
|
|
136
|
+
const budget = ethers_1.ethers.formatUnits(rule.rule.budget, 18);
|
|
137
|
+
const used = ethers_1.ethers.formatUnits(rule.usage.amountUsed, 18);
|
|
138
|
+
const windowType = timeWindow === 0n ? "per-tx" : `${Number(timeWindow) / 86400} day(s)`;
|
|
139
|
+
console.log(` Rule ${i}: ${windowType}, budget: ${budget}, used: ${used}`);
|
|
140
|
+
}
|
|
141
|
+
return { isSupported, balance };
|
|
142
|
+
}
|
|
143
|
+
async function querySessionStatus(aaWallet, sessionId, provider) {
|
|
144
|
+
const contract = new ethers_1.ethers.Contract(aaWallet, gokiteAccountInterface, provider);
|
|
145
|
+
console.log("\n[Query] Session Status");
|
|
146
|
+
console.log("=".repeat(50));
|
|
147
|
+
// 1. Check if session exists
|
|
148
|
+
const exists = await contract.sessionExists(sessionId);
|
|
149
|
+
console.log(` Session ${sessionId.slice(0, 18)}... exists: ${exists}`);
|
|
150
|
+
if (!exists)
|
|
151
|
+
return { exists };
|
|
152
|
+
// 2. Get session agent
|
|
153
|
+
const agent = await contract.getSessionAgent(sessionId);
|
|
154
|
+
console.log(` Session agent: ${agent}`);
|
|
155
|
+
// 3. Get spending rules
|
|
156
|
+
const spendingRules = await contract.getSpendingRules(sessionId);
|
|
157
|
+
console.log(` Spending rules count: ${spendingRules.length}`);
|
|
158
|
+
for (let i = 0; i < spendingRules.length; i++) {
|
|
159
|
+
const rule = spendingRules[i];
|
|
160
|
+
const timeWindow = rule.rule.timeWindow;
|
|
161
|
+
const budget = ethers_1.ethers.formatUnits(rule.rule.budget, 18);
|
|
162
|
+
const used = ethers_1.ethers.formatUnits(rule.usage.amountUsed, 18);
|
|
163
|
+
const windowType = timeWindow === 0n ? "per-tx" : `${Number(timeWindow) / 86400} day(s)`;
|
|
164
|
+
console.log(` Rule ${i}: ${windowType}, budget: ${budget}, used: ${used}`);
|
|
165
|
+
}
|
|
166
|
+
// 4. Check if a specific amount would pass spending rules
|
|
167
|
+
const testAmount = ethers_1.ethers.parseUnits("5", 18);
|
|
168
|
+
const wouldPass = await contract.checkSpendingRules(sessionId, testAmount, ethers_1.ethers.ZeroHash);
|
|
169
|
+
console.log(` Would ${ethers_1.ethers.formatUnits(testAmount, 18)} pass spending rules: ${wouldPass}`);
|
|
170
|
+
return { exists, agent, spendingRules };
|
|
171
|
+
}
|
|
172
|
+
async function checkNonceStatus(aaWallet, nonce, provider) {
|
|
173
|
+
const contract = new ethers_1.ethers.Contract(aaWallet, gokiteAccountInterface, provider);
|
|
174
|
+
const isUsed = await contract.isNonceUsed(nonce);
|
|
175
|
+
console.log(` Nonce ${nonce.slice(0, 18)}... used: ${isUsed}`);
|
|
176
|
+
return isUsed;
|
|
177
|
+
}
|
|
101
178
|
async function main() {
|
|
102
|
-
const aaWallet = sdk.getAccountAddress(
|
|
103
|
-
const
|
|
104
|
-
|
|
179
|
+
const aaWallet = sdk.getAccountAddress(ownerSigner.address);
|
|
180
|
+
const provider = new ethers_1.ethers.JsonRpcProvider(RPC_URL);
|
|
181
|
+
const chainId = (await provider.getNetwork()).chainId;
|
|
182
|
+
// ============ Frontend Query Examples ============
|
|
183
|
+
await queryWalletStatus(aaWallet, provider);
|
|
184
|
+
// ============ Step 1: Setup AA Wallet (batch: addSupportedToken + setMasterBudgetRules) ============
|
|
185
|
+
console.log("\n[Step 1] Setting up AA wallet...");
|
|
186
|
+
const addTokenData = gokiteAccountInterface.encodeFunctionData("addSupportedToken", [
|
|
105
187
|
SETTLEMENT_TOKEN,
|
|
106
|
-
initialRules(),
|
|
107
188
|
]);
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const updateRulesData = sessionInterface.encodeFunctionData("setSpendingRules", [
|
|
112
|
-
sessionId,
|
|
113
|
-
updatedRules(),
|
|
189
|
+
const masterBudgetData = gokiteAccountInterface.encodeFunctionData("setMasterBudgetRules", [
|
|
190
|
+
[BigInt(SECONDS_PER_DAY), 0n],
|
|
191
|
+
[ethers_1.ethers.parseUnits("1000", 18), ethers_1.ethers.parseUnits("100", 18)], // 1000 USD/day, 100 USD/tx
|
|
114
192
|
]);
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const transferData = erc20Interface.encodeFunctionData("transfer", [
|
|
120
|
-
RECIPIENT,
|
|
121
|
-
ethers_1.ethers.parseUnits("0", 18),
|
|
193
|
+
const setupBatchData = gokiteAccountInterface.encodeFunctionData("executeBatch", [
|
|
194
|
+
[aaWallet, aaWallet],
|
|
195
|
+
[],
|
|
196
|
+
[addTokenData, masterBudgetData],
|
|
122
197
|
]);
|
|
123
|
-
const
|
|
124
|
-
console.log(`
|
|
198
|
+
// const setupTx = await sendWithTokenPayment(aaWallet, setupBatchData);
|
|
199
|
+
// console.log(` Setup (addSupportedToken + setMasterBudgetRules) tx: ${setupTx}`);
|
|
200
|
+
// await new Promise((r) => setTimeout(r, 5000));
|
|
201
|
+
// ============ Step 2: Create Session ============
|
|
202
|
+
console.log("\n[Step 2] Creating session...");
|
|
203
|
+
const createSessionData = gokiteAccountInterface.encodeFunctionData("createSession", [
|
|
204
|
+
sessionId,
|
|
205
|
+
agentSigner.address,
|
|
206
|
+
sessionRules(),
|
|
207
|
+
]);
|
|
208
|
+
const createSessionTx = await sendWithTokenPayment(aaWallet, createSessionData);
|
|
209
|
+
console.log(` Session ID: ${sessionId}`);
|
|
210
|
+
console.log(` createSession tx: ${createSessionTx}`);
|
|
211
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
212
|
+
// Query session status after creation
|
|
213
|
+
await querySessionStatus(aaWallet, sessionId, provider);
|
|
214
|
+
// ============ Step 3: Sign Transfer Authorization ============
|
|
215
|
+
console.log("\n[Step 3] Agent signing transfer authorization...");
|
|
216
|
+
const recipient = ownerSigner.address; // send to self for demo
|
|
217
|
+
const transferAmount = ethers_1.ethers.parseUnits("1", 18); // 1 token
|
|
218
|
+
const nonce = ethers_1.ethers.hexlify((0, ethers_1.randomBytes)(32));
|
|
219
|
+
const now = BigInt(Math.floor(Date.now() / 1000));
|
|
220
|
+
const auth = {
|
|
221
|
+
from: aaWallet,
|
|
222
|
+
to: recipient,
|
|
223
|
+
token: SETTLEMENT_TOKEN,
|
|
224
|
+
value: transferAmount,
|
|
225
|
+
validAfter: now - 60n,
|
|
226
|
+
validBefore: now + 3600n,
|
|
227
|
+
nonce: nonce,
|
|
228
|
+
};
|
|
229
|
+
const signature = await signTransferAuthorization(aaWallet, chainId, auth);
|
|
230
|
+
console.log(` Authorization nonce: ${nonce}`);
|
|
231
|
+
console.log(` Signature: ${signature.slice(0, 42)}...`);
|
|
232
|
+
// ============ Step 4: Execute Transfer ============
|
|
233
|
+
console.log("\n[Step 4] Executing transfer with authorization...");
|
|
234
|
+
const executeTransferData = gokiteAccountInterface.encodeFunctionData("executeTransferWithAuthorization", [sessionId, auth, signature]);
|
|
235
|
+
const executeTx = await sendWithTokenPayment(aaWallet, executeTransferData);
|
|
236
|
+
console.log(` executeTransferWithAuthorization tx: ${executeTx}`);
|
|
237
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
238
|
+
// ============ Post-Transfer Queries ============
|
|
239
|
+
console.log("\n[Post-Transfer] Checking status...");
|
|
240
|
+
// Check nonce is now used (replay protection)
|
|
241
|
+
await checkNonceStatus(aaWallet, nonce, provider);
|
|
242
|
+
// Query updated session status (usage should be updated)
|
|
243
|
+
await querySessionStatus(aaWallet, sessionId, provider);
|
|
244
|
+
// Query updated wallet status
|
|
245
|
+
await queryWalletStatus(aaWallet, provider);
|
|
246
|
+
console.log("\n" + "=".repeat(60));
|
|
247
|
+
console.log("E2E Demo completed successfully!");
|
|
248
|
+
console.log("=".repeat(60));
|
|
125
249
|
}
|
|
126
250
|
main().catch((error) => {
|
|
127
251
|
console.error(error);
|