gokite-aa-sdk 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.js +5 -1
- package/dist/example-token-paymaster.d.ts +1 -0
- package/dist/example-token-paymaster.js +33 -0
- package/dist/example.d.ts +158 -24
- package/dist/example.js +325 -226
- package/dist/gokite-aa-sdk.d.ts +24 -3
- package/dist/gokite-aa-sdk.js +152 -6
- package/dist/types.d.ts +17 -0
- package/dist/utils.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ import { ethers } from 'ethers';
|
|
|
18
18
|
const sdk = new GokiteAASDK(
|
|
19
19
|
'kite_testnet',
|
|
20
20
|
'https://rpc-testnet.gokite.ai',
|
|
21
|
-
'
|
|
21
|
+
'http://localhost:14337/rpc/' // bundler URL
|
|
22
22
|
);
|
|
23
23
|
|
|
24
24
|
// Owner address (from your social login SDK)
|
package/dist/config.d.ts
CHANGED
|
@@ -5,5 +5,10 @@ export interface NetworkConfig {
|
|
|
5
5
|
accountImplementation: string;
|
|
6
6
|
bundlerUrl?: string;
|
|
7
7
|
paymaster?: string;
|
|
8
|
+
supportedTokens: {
|
|
9
|
+
address: string;
|
|
10
|
+
symbol: string;
|
|
11
|
+
decimals: number;
|
|
12
|
+
}[];
|
|
8
13
|
}
|
|
9
14
|
export declare const NETWORKS: Record<string, NetworkConfig>;
|
package/dist/config.js
CHANGED
|
@@ -7,6 +7,10 @@ exports.NETWORKS = {
|
|
|
7
7
|
entryPoint: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108',
|
|
8
8
|
accountFactory: '0xF0Fc19F0dc393867F19351d25EDfc5E099561cb7',
|
|
9
9
|
accountImplementation: '0x93F5310eFd0f09db0666CA5146E63CA6Cdc6FC21',
|
|
10
|
-
paymaster: '
|
|
10
|
+
paymaster: '0x9Adcbf85D5c724611a490Ba9eDc4d38d6F39e92d',
|
|
11
|
+
supportedTokens: [
|
|
12
|
+
{ address: '0x0000000000000000000000000000000000000000', symbol: 'KITE', decimals: 18 },
|
|
13
|
+
{ address: '0x0fF5393387ad2f9f691FD6Fd28e07E3969e27e63', symbol: 'Test USD', decimals: 18 },
|
|
14
|
+
],
|
|
11
15
|
},
|
|
12
16
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ethers_1 = require("ethers");
|
|
4
|
+
const gokite_aa_sdk_1 = require("./gokite-aa-sdk");
|
|
5
|
+
async function simpleExample() {
|
|
6
|
+
const sdk = new gokite_aa_sdk_1.GokiteAASDK('kite_testnet', 'https://rpc-testnet.gokite.ai', 'https://bundler-service.staging.gokite.ai/rpc/');
|
|
7
|
+
const signer = new ethers_1.ethers.Wallet(process.env.PRIVATE_KEY);
|
|
8
|
+
const request = {
|
|
9
|
+
target: '0x0000000000000000000000000000000000000000',
|
|
10
|
+
value: BigInt(0),
|
|
11
|
+
callData: '0x'
|
|
12
|
+
};
|
|
13
|
+
console.log('📊 Estimate transaction...');
|
|
14
|
+
const estimate = await sdk.estimateUserOperation(signer.address, request);
|
|
15
|
+
console.log('Total cost:', estimate.totalCostKITEFormatted);
|
|
16
|
+
console.log('Cost with token:', estimate.supportedTokens);
|
|
17
|
+
console.log('Sponsorship available:', estimate.sponsorshipAvailable);
|
|
18
|
+
console.log('Remaining sponsorships:', estimate.remainingSponsorships);
|
|
19
|
+
console.log('Payment options:', estimate.supportedTokens.map(t => `${t.tokenSymbol}: ${t.formattedCost}`));
|
|
20
|
+
// Step 2: Select payment method and execute
|
|
21
|
+
const tokenAddress = estimate.sponsorshipAvailable
|
|
22
|
+
? '0x0000000000000000000000000000000000000000' // Use sponsorship/KITE
|
|
23
|
+
: '0x0fF5393387ad2f9f691FD6Fd28e07E3969e27e63'; // Use Test USD
|
|
24
|
+
console.log('\n🚀 Execute transaction...');
|
|
25
|
+
const result = await sdk.sendUserOperationWithPayment(signer.address, request, estimate.userOp, tokenAddress, async (hash) => signer.signMessage(ethers_1.ethers.getBytes(hash)));
|
|
26
|
+
console.log('✅ Done!');
|
|
27
|
+
console.log('UserOp Hash:', result.userOpHash);
|
|
28
|
+
console.log('Status:', result.status.status);
|
|
29
|
+
}
|
|
30
|
+
// Run
|
|
31
|
+
if (require.main === module) {
|
|
32
|
+
simpleExample().catch(console.error);
|
|
33
|
+
}
|
package/dist/example.d.ts
CHANGED
|
@@ -1,47 +1,181 @@
|
|
|
1
1
|
import { GokiteAASDK } from './gokite-aa-sdk';
|
|
2
2
|
import "dotenv/config";
|
|
3
|
+
declare function initializeSDK(): GokiteAASDK;
|
|
3
4
|
declare function handleAAError(error: any): {
|
|
4
5
|
type: string;
|
|
5
6
|
message: string;
|
|
6
7
|
details?: any;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
+
* Transfer ERC20 tokens from AA wallet to another address
|
|
11
|
+
* 从AA钱包转账ERC20代币到其他地址
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* @param eoa - User's EOA address (from Privy)
|
|
14
|
+
* @param to - Recipient address
|
|
15
|
+
* @param amount - Amount to transfer
|
|
16
|
+
* @param tokenAddress - Token contract address
|
|
17
|
+
* @param tokenDecimals - Token decimals (usually 18)
|
|
18
|
+
* @param signFunction - Signing function from Privy
|
|
19
|
+
*/
|
|
20
|
+
export declare function transferERC20(eoa: string, to: string, amount: string, tokenAddress: string, tokenDecimals: number | undefined, signFunction: (userOpHash: string) => Promise<string>): Promise<{
|
|
21
|
+
success: boolean;
|
|
22
|
+
transactionHash: string | undefined;
|
|
23
|
+
message: string;
|
|
24
|
+
error?: undefined;
|
|
25
|
+
} | {
|
|
26
|
+
success: boolean;
|
|
27
|
+
error: string | undefined;
|
|
28
|
+
message: string;
|
|
29
|
+
transactionHash?: undefined;
|
|
30
|
+
} | {
|
|
31
|
+
success: boolean;
|
|
32
|
+
error: {
|
|
33
|
+
type: string;
|
|
34
|
+
message: string;
|
|
35
|
+
details?: any;
|
|
36
|
+
};
|
|
37
|
+
message: string;
|
|
38
|
+
transactionHash?: undefined;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* 部署KitePass合约
|
|
17
42
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
43
|
+
* @param eoa - User's EOA address
|
|
44
|
+
* @param signFunction - Signing function from Privy
|
|
45
|
+
* @returns Deployed KitePass address
|
|
20
46
|
*/
|
|
21
|
-
declare function
|
|
47
|
+
export declare function deployKitePass(eoa: string, signFunction: (userOpHash: string) => Promise<string>): Promise<{
|
|
48
|
+
success: boolean;
|
|
49
|
+
proxyAddress?: string;
|
|
50
|
+
error?: any;
|
|
51
|
+
}>;
|
|
22
52
|
/**
|
|
23
|
-
*
|
|
53
|
+
* Configure spending rules for KitePass
|
|
54
|
+
* setSpendingRules方法会先清除现有的rules,然后设置新的rules
|
|
55
|
+
* 1. 新增/修改:需要先获取现有的rules,然后append新的rules,或者在现有rules的基础上修改
|
|
56
|
+
* 2. 删除:需要先获取现有的rules,然后删除对应的rules
|
|
57
|
+
*
|
|
58
|
+
* @param eoa - User's EOA address
|
|
59
|
+
* @param kitepassAddress - KitePass proxy address
|
|
60
|
+
* @param rules - Spending rules to configure
|
|
61
|
+
* @param signFunction - Signing function
|
|
62
|
+
*/
|
|
63
|
+
export declare function configureSpendingRules(eoa: string, kitepassAddress: string, signFunction: (userOpHash: string) => Promise<string>): Promise<{
|
|
64
|
+
success: boolean;
|
|
65
|
+
transactionHash: string | undefined;
|
|
66
|
+
error?: undefined;
|
|
67
|
+
} | {
|
|
68
|
+
success: boolean;
|
|
69
|
+
error: string | undefined;
|
|
70
|
+
transactionHash?: undefined;
|
|
71
|
+
} | {
|
|
72
|
+
success: boolean;
|
|
73
|
+
error: {
|
|
74
|
+
type: string;
|
|
75
|
+
message: string;
|
|
76
|
+
details?: any;
|
|
77
|
+
};
|
|
78
|
+
transactionHash?: undefined;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* View current spending rules (read-only)
|
|
82
|
+
* 查看当前消费规则(只读)
|
|
24
83
|
*/
|
|
25
|
-
declare function
|
|
84
|
+
export declare function viewSpendingRules(kitepassAddress: string): Promise<{
|
|
85
|
+
success: boolean;
|
|
86
|
+
rules: any;
|
|
87
|
+
error?: undefined;
|
|
88
|
+
} | {
|
|
89
|
+
success: boolean;
|
|
90
|
+
error: string;
|
|
91
|
+
rules?: undefined;
|
|
92
|
+
}>;
|
|
26
93
|
/**
|
|
27
|
-
*
|
|
94
|
+
* Withdraw funds from KitePass
|
|
95
|
+
* 从KitePass提取资金
|
|
28
96
|
*/
|
|
29
|
-
declare function
|
|
97
|
+
export declare function withdrawFunds(eoa: string, kitepassAddress: string, amount: string, tokenAddress: string | undefined, signFunction: (userOpHash: string) => Promise<string>): Promise<{
|
|
98
|
+
success: boolean;
|
|
99
|
+
transactionHash: string | undefined;
|
|
100
|
+
error?: undefined;
|
|
101
|
+
} | {
|
|
102
|
+
success: boolean;
|
|
103
|
+
error: string | undefined;
|
|
104
|
+
transactionHash?: undefined;
|
|
105
|
+
} | {
|
|
106
|
+
success: boolean;
|
|
107
|
+
error: {
|
|
108
|
+
type: string;
|
|
109
|
+
message: string;
|
|
110
|
+
details?: any;
|
|
111
|
+
};
|
|
112
|
+
transactionHash?: undefined;
|
|
113
|
+
}>;
|
|
30
114
|
/**
|
|
31
|
-
*
|
|
115
|
+
* Check token balance (read-only)
|
|
116
|
+
* 查看代币余额(只读)
|
|
32
117
|
*/
|
|
33
|
-
declare function
|
|
118
|
+
export declare function checkTokenBalance(address: string, tokenAddress?: string): Promise<{
|
|
119
|
+
success: boolean;
|
|
120
|
+
balance: string;
|
|
121
|
+
symbol: any;
|
|
122
|
+
decimals: any;
|
|
123
|
+
address: string;
|
|
124
|
+
error?: undefined;
|
|
125
|
+
} | {
|
|
126
|
+
success: boolean;
|
|
127
|
+
error: string;
|
|
128
|
+
balance?: undefined;
|
|
129
|
+
symbol?: undefined;
|
|
130
|
+
decimals?: undefined;
|
|
131
|
+
address?: undefined;
|
|
132
|
+
}>;
|
|
133
|
+
export interface ServiceInfo {
|
|
134
|
+
serviceOwner: string;
|
|
135
|
+
priceModel: number;
|
|
136
|
+
unitPrice: number;
|
|
137
|
+
provider: string;
|
|
138
|
+
metadata: string;
|
|
139
|
+
name: string;
|
|
140
|
+
isPublic: boolean;
|
|
141
|
+
}
|
|
34
142
|
/**
|
|
35
|
-
*
|
|
143
|
+
* Register a service in the service registry
|
|
144
|
+
* 在服务注册表中注册服务
|
|
145
|
+
*
|
|
146
|
+
* @param eoa - Service owner's EOA address
|
|
147
|
+
* @param serviceId - Service ID (bytes32)
|
|
148
|
+
* @param serviceInfo - Service information
|
|
149
|
+
* @param signFunction - Signing function
|
|
36
150
|
*/
|
|
37
|
-
declare function
|
|
151
|
+
export declare function registerService(eoa: string, serviceId: string, serviceInfo: ServiceInfo, signFunction: (userOpHash: string) => Promise<string>): Promise<{
|
|
152
|
+
success: boolean;
|
|
153
|
+
transactionHash: string | undefined;
|
|
154
|
+
serviceId: string;
|
|
155
|
+
error?: undefined;
|
|
156
|
+
} | {
|
|
157
|
+
success: boolean;
|
|
158
|
+
error: string | undefined;
|
|
159
|
+
transactionHash?: undefined;
|
|
160
|
+
serviceId?: undefined;
|
|
161
|
+
} | {
|
|
162
|
+
success: boolean;
|
|
163
|
+
error: {
|
|
164
|
+
type: string;
|
|
165
|
+
message: string;
|
|
166
|
+
details?: any;
|
|
167
|
+
};
|
|
168
|
+
transactionHash?: undefined;
|
|
169
|
+
serviceId?: undefined;
|
|
170
|
+
}>;
|
|
38
171
|
/**
|
|
39
|
-
*
|
|
172
|
+
* Get TransparentUpgradeableProxy bytecode
|
|
173
|
+
* 获取透明可升级代理字节码
|
|
40
174
|
*/
|
|
41
|
-
declare function
|
|
175
|
+
declare function getTransparentProxyBytecode(): string;
|
|
42
176
|
/**
|
|
43
|
-
*
|
|
177
|
+
* Parse ContractCreated event from transaction logs
|
|
178
|
+
* 从交易日志中解析合约创建事件
|
|
44
179
|
*/
|
|
45
|
-
declare function
|
|
46
|
-
export {
|
|
47
|
-
export declare const example: typeof basicExample;
|
|
180
|
+
declare function parseContractCreatedEvent(transactionHash: string): Promise<string | null>;
|
|
181
|
+
export { initializeSDK, handleAAError, getTransparentProxyBytecode, parseContractCreatedEvent };
|
package/dist/example.js
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.parseContractCreatedEvent = exports.getTransparentProxyBytecode = exports.handleAAError = exports.initializeSDK = exports.registerService = exports.checkTokenBalance = exports.withdrawFunds = exports.viewSpendingRules = exports.configureSpendingRules = exports.deployKitePass = exports.transferERC20 = void 0;
|
|
4
4
|
const gokite_aa_sdk_1 = require("./gokite-aa-sdk");
|
|
5
5
|
const types_1 = require("./types");
|
|
6
6
|
const ethers_1 = require("ethers");
|
|
7
7
|
require("dotenv/config");
|
|
8
|
+
/**
|
|
9
|
+
* Sections:
|
|
10
|
+
* 1. General Use - ERC20 Token Transfer (通用功能 - ERC20代币转账)
|
|
11
|
+
* 2. KitePass Related - ClientAgentVault Operations (KitePass相关 - 客户端代理金库操作)
|
|
12
|
+
* 3. Subnet Related - Subnet Management (子网相关 - 子网管理)
|
|
13
|
+
* 4. Service Related - Service Registry Operations (服务相关 - 服务注册表操作)
|
|
14
|
+
*/
|
|
8
15
|
// Contract addresses on kite_testnet
|
|
9
16
|
const ADDRESSES = {
|
|
10
17
|
SETTLEMENT_TOKEN: '0x0fF5393387ad2f9f691FD6Fd28e07E3969e27e63',
|
|
11
18
|
SETTLEMENT_CONTRACT: '0x8d9FaD78d5Ce247aA01C140798B9558fd64a63E3',
|
|
12
|
-
CLIENT_AGENT_VAULT_IMPL: '0xB5AAFCC6DD4DFc2B80fb8BCcf406E1a2Fd559e23'
|
|
19
|
+
CLIENT_AGENT_VAULT_IMPL: '0xB5AAFCC6DD4DFc2B80fb8BCcf406E1a2Fd559e23',
|
|
20
|
+
SERVICE_REGISTRY: '0xF727EDE22C9e338a7d1d57B930dcEBbC6a66c008'
|
|
13
21
|
};
|
|
22
|
+
// SDK initialization - 初始化SDK
|
|
23
|
+
function initializeSDK() {
|
|
24
|
+
return new gokite_aa_sdk_1.GokiteAASDK('kite_testnet', 'https://rpc-testnet.gokite.ai', 'https://bundler-service.staging.gokite.ai/rpc/');
|
|
25
|
+
}
|
|
26
|
+
exports.initializeSDK = initializeSDK;
|
|
14
27
|
function handleAAError(error) {
|
|
15
28
|
if (error instanceof types_1.AASDKError) {
|
|
16
29
|
return {
|
|
@@ -19,7 +32,6 @@ function handleAAError(error) {
|
|
|
19
32
|
details: error.details
|
|
20
33
|
};
|
|
21
34
|
}
|
|
22
|
-
// Fallback for other errors
|
|
23
35
|
return {
|
|
24
36
|
type: 'UNKNOWN_ERROR',
|
|
25
37
|
message: error.message || 'An unknown error occurred',
|
|
@@ -27,142 +39,146 @@ function handleAAError(error) {
|
|
|
27
39
|
};
|
|
28
40
|
}
|
|
29
41
|
exports.handleAAError = handleAAError;
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// 1. GENERAL USE - ERC20 Token Transfer
|
|
44
|
+
// 通用功能 - ERC20代币转账
|
|
45
|
+
// =============================================================================
|
|
30
46
|
/**
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
function getTransparentProxyBytecode() {
|
|
34
|
-
// This is the bytecode for TransparentUpgradeableProxy
|
|
35
|
-
return '0x60a0604052610b278038038061001481610293565b928339810160608282031261028e5761002c826102b8565b610038602084016102b8565b604084015190936001600160401b03821161028e570182601f8201121561028e5780519061006d610068836102cc565b610293565b938285526020838301011161028e5760005b828110610279575050602060009184010152803b15610258577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0383169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a281511561023f5760008083602061013595519101845af43d15610237573d91610125610068846102cc565b9283523d6000602085013e6102e7565b505b604051906104918083016001600160401b0381118482101761022157602092849261067684396001600160a01b031681520301906000f080156102155760018060a01b031680608052600080516020610b07833981519152547f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6040805160018060a01b0384168152846020820152a181156101ff576001600160a01b03191617600080516020610b078339815191525560405161032d908161034982396080518160070152f35b633173bdd160e11b600052600060045260246000fd5b6040513d6000823e3d90fd5b634e487b7160e01b600052604160045260246000fd5b6060916102e7565b505034156101375763b398979f60e01b60005260046000fd5b634c9c8ce360e01b60009081526001600160a01b0391909116600452602490fd5b8060208092840101518282880101520161007f565b600080fd5b6040519190601f01601f191682016001600160401b0381118382101761022157604052565b51906001600160a01b038216820361028e57565b6001600160401b03811161022157601f01601f191660200190565b9061030d57508051156102fc57805190602001fd5b630a12f52160e11b60005260046000fd5b8151158061033f575b61031e575090565b639996b31560e01b60009081526001600160a01b0391909116600452602490fd5b50803b1561031656fe6080604052337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031603610069576000356001600160e01b03191663278f794360e11b1461005f576334ad5dbb60e21b60005260046000fd5b610067610113565b005b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5460009081906001600160a01b0316368280378136915af43d6000803e156100b1573d6000f35b3d6000fd5b634e487b7160e01b600052604160045260246000fd5b6040519190601f01601f1916820167ffffffffffffffff8111838210176100f257604052565b6100b6565b67ffffffffffffffff81116100f257601f01601f191660200190565b3660041161019d57604036600319011261019d576004356001600160a01b0381169081900361019d576024359067ffffffffffffffff821161019d573660238301121561019d5781600401359061017161016c836100f7565b6100cc565b91808352366024828601011161019d57602081600092602461019b970183870137840101526101a2565b565b600080fd5b90813b15610239577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a280511561021f5761021c9161025b565b50565b50503461022857565b63b398979f60e01b60005260046000fd5b50634c9c8ce360e01b60009081526001600160a01b0391909116600452602490fd5b60008061028f93602081519101845af43d15610292573d9161027f61016c846100f7565b9283523d6000602085013e610296565b90565b6060915b906102bc57508051156102ab57805190602001fd5b630a12f52160e11b60005260046000fd5b815115806102ee575b6102cd575090565b639996b31560e01b60009081526001600160a01b0391909116600452602490fd5b50803b156102c556fea2646970667358221220597147005a6fe654561cbab25a93153cc233180473a65a90bd427f0c1f41018764736f6c634300081c003360803460bc57601f61049138819003918201601f19168301916001600160401b0383118484101760c15780849260209460405283398101031260bc57516001600160a01b0381169081900360bc57801560a657600080546001600160a01b031981168317825560405192916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36103b990816100d88239f35b631e4fbdf760e01b600052600060045260246000fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe6080604052600436101561001257600080fd5b6000803560e01c8063715018a6146102875780638da5cb5b146102605780639623609d1461012f578063ad3cb1cc146100e25763f2fde38b1461005457600080fd5b346100df5760203660031901126100df576004356001600160a01b038116908190036100dd5761008261035a565b80156100c95781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b505b80fd5b50346100df57806003193601126100df575061012b6040516101056040826102e1565b60058152640352e302e360dc1b6020820152604051918291602083526020830190610319565b0390f35b5060603660031901126100df576004356001600160a01b038116908190036100dd576024356001600160a01b038116908190036102405760443567ffffffffffffffff811161025c573660238201121561025c57806004013567ffffffffffffffff8111610248576040518593929091906101b4601f8301601f1916602001846102e1565b81835236602483830101116102445781859260246020930183860137830101526101dc61035a565b833b156102405761021293839260405180968194829363278f794360e11b84526004840152604060248401526044830190610319565b039134905af18015610233576102255780f35b61022e916102e1565b388180f35b50604051903d90823e3d90fd5b8280fd5b8480fd5b634e487b7160e01b85526041600452602485fd5b8380fd5b50346100df57806003193601126100df57546040516001600160a01b039091168152602090f35b50346100df57806003193601126100df576102a061035a565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b90601f8019910116810190811067ffffffffffffffff82111761030357604052565b634e487b7160e01b600052604160045260246000fd5b919082519283825260005b848110610345575050826000602080949584010152601f8019910116010190565b80602080928401015182828601015201610324565b6000546001600160a01b0316330361036e57565b63118cdaa760e01b6000523360045260246000fdfea26469706673582212207bc32ec723f6be34b228b59aef2ef61b4e6a8eb5bc67fcdd495248566e3b6e0c64736f6c634300081c0033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103';
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* ClientAgentVault Integration Tests
|
|
47
|
+
* Transfer ERC20 tokens from AA wallet to another address
|
|
48
|
+
* 从AA钱包转账ERC20代币到其他地址
|
|
39
49
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* how to use:
|
|
48
|
-
* - modify the contract addresses and parameters as needed
|
|
50
|
+
* @param eoa - User's EOA address (from Privy)
|
|
51
|
+
* @param to - Recipient address
|
|
52
|
+
* @param amount - Amount to transfer
|
|
53
|
+
* @param tokenAddress - Token contract address
|
|
54
|
+
* @param tokenDecimals - Token decimals (usually 18)
|
|
55
|
+
* @param signFunction - Signing function from Privy
|
|
49
56
|
*/
|
|
50
|
-
async function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
async function transferERC20(eoa, to, amount, tokenAddress, tokenDecimals = 18, signFunction) {
|
|
58
|
+
const sdk = initializeSDK();
|
|
59
|
+
try {
|
|
60
|
+
const transferRequest = {
|
|
61
|
+
target: tokenAddress,
|
|
62
|
+
value: 0n,
|
|
63
|
+
callData: ethers_1.ethers.Interface.from([
|
|
64
|
+
'function transfer(address to, uint256 amount)'
|
|
65
|
+
]).encodeFunctionData('transfer', [
|
|
66
|
+
to,
|
|
67
|
+
ethers_1.ethers.parseUnits(amount, tokenDecimals)
|
|
68
|
+
])
|
|
69
|
+
};
|
|
70
|
+
console.log(`Transferring ${amount} tokens to ${to}...`);
|
|
71
|
+
const result = await sdk.sendUserOperationAndWait(eoa, transferRequest, signFunction);
|
|
72
|
+
if (result.status.status === 'success') {
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
transactionHash: result.status.transactionHash,
|
|
76
|
+
message: 'Transfer successful'
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: result.status.reason,
|
|
83
|
+
message: 'Transfer failed'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const aaError = handleAAError(error);
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
error: aaError,
|
|
92
|
+
message: 'Transfer failed'
|
|
93
|
+
};
|
|
70
94
|
}
|
|
71
|
-
console.log(`✅ Using deployed proxy address: ${deployedProxyAddress}`);
|
|
72
|
-
// 2. Configure Spending Rules
|
|
73
|
-
console.log('\n--- Test 2: Configure Spending Rules ---');
|
|
74
|
-
await ConfigureSpendingRules(sdk, eoa, deployedProxyAddress, signFunction);
|
|
75
|
-
// 3. View Spending Rules
|
|
76
|
-
console.log('\n--- Test 3: View Spending Rules ---');
|
|
77
|
-
await ViewSpendingRules(sdk, deployedProxyAddress);
|
|
78
|
-
// 4. Withdraw Funds
|
|
79
|
-
console.log('\n--- Test 4: Withdraw Funds ---');
|
|
80
|
-
await WithdrawFunds(sdk, eoa, deployedProxyAddress, signFunction);
|
|
81
|
-
// 5. Check Token Balance
|
|
82
|
-
console.log('\n--- Test 5: Check Token Balance ---');
|
|
83
|
-
await CheckTokenBalance(sdk, deployedProxyAddress);
|
|
84
95
|
}
|
|
85
|
-
exports.
|
|
96
|
+
exports.transferERC20 = transferERC20;
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// 2. KITEPASS RELATED - ClientAgentVault Operations
|
|
99
|
+
// KitePass相关 - 客户端代理金库操作
|
|
100
|
+
// =============================================================================
|
|
86
101
|
/**
|
|
87
|
-
*
|
|
102
|
+
* 部署KitePass合约
|
|
103
|
+
*
|
|
104
|
+
* @param eoa - User's EOA address
|
|
105
|
+
* @param signFunction - Signing function from Privy
|
|
106
|
+
* @returns Deployed KitePass address
|
|
88
107
|
*/
|
|
89
|
-
async function
|
|
108
|
+
async function deployKitePass(eoa, signFunction) {
|
|
109
|
+
const sdk = initializeSDK();
|
|
110
|
+
const aa = sdk.getAccountAddress(eoa);
|
|
90
111
|
try {
|
|
91
|
-
// Prepare initialization data
|
|
112
|
+
// Prepare initialization data
|
|
92
113
|
const initializeCallData = ethers_1.ethers.Interface.from([
|
|
93
114
|
'function initialize(address allowedToken, address owner)'
|
|
94
115
|
]).encodeFunctionData('initialize', [
|
|
95
116
|
ADDRESSES.SETTLEMENT_TOKEN,
|
|
96
|
-
aa
|
|
97
|
-
]);
|
|
98
|
-
// Create TransparentUpgradeableProxy deployment bytecode
|
|
99
|
-
// constructor(address _logic, address admin_, bytes memory _data)
|
|
100
|
-
const proxyConstructorData = ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(['address', 'address', 'bytes'], [
|
|
101
|
-
ADDRESSES.CLIENT_AGENT_VAULT_IMPL,
|
|
102
|
-
aa,
|
|
103
|
-
initializeCallData // initialization data
|
|
117
|
+
aa
|
|
104
118
|
]);
|
|
105
|
-
//
|
|
119
|
+
// Create KitePass deployment bytecode
|
|
120
|
+
const proxyConstructorData = ethers_1.ethers.AbiCoder.defaultAbiCoder().encode(['address', 'address', 'bytes'], [ADDRESSES.CLIENT_AGENT_VAULT_IMPL, aa, initializeCallData]);
|
|
106
121
|
const transparentProxyBytecode = getTransparentProxyBytecode();
|
|
107
122
|
const fullInitCode = transparentProxyBytecode + proxyConstructorData.slice(2);
|
|
108
|
-
// Create UserOperation to call performCreate
|
|
109
123
|
const deployRequest = {
|
|
110
124
|
target: aa,
|
|
111
125
|
value: 0n,
|
|
112
126
|
callData: ethers_1.ethers.Interface.from([
|
|
113
127
|
'function performCreate(uint256 value, bytes calldata initCode) returns (address)'
|
|
114
|
-
]).encodeFunctionData('performCreate', [
|
|
115
|
-
0n,
|
|
116
|
-
fullInitCode // proxy deployment bytecode + constructor params
|
|
117
|
-
])
|
|
128
|
+
]).encodeFunctionData('performCreate', [0n, fullInitCode])
|
|
118
129
|
};
|
|
119
|
-
console.log('Deploying
|
|
120
|
-
console.log('- Implementation:', ADDRESSES.CLIENT_AGENT_VAULT_IMPL);
|
|
121
|
-
console.log('- AllowedToken:', ADDRESSES.SETTLEMENT_TOKEN);
|
|
130
|
+
console.log('Deploying KitePass proxy...');
|
|
122
131
|
const result = await sdk.sendUserOperationAndWait(eoa, deployRequest, signFunction);
|
|
123
132
|
if (result.status.status === 'success') {
|
|
124
|
-
console.log('✅ Proxy deployed successfully!');
|
|
125
|
-
console.log('Transaction hash:', result.status.transactionHash);
|
|
126
|
-
// Parse the deployed proxy address from transaction logs
|
|
127
133
|
const proxyAddress = await parseContractCreatedEvent(result.status.transactionHash);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
console.log('⚠️ Could not parse proxy address from logs');
|
|
134
|
-
}
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
proxyAddress: proxyAddress || undefined
|
|
137
|
+
};
|
|
135
138
|
}
|
|
136
139
|
else {
|
|
137
|
-
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
error: result.status.reason
|
|
143
|
+
};
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
catch (error) {
|
|
141
|
-
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
error: handleAAError(error)
|
|
150
|
+
};
|
|
142
151
|
}
|
|
143
|
-
return null;
|
|
144
152
|
}
|
|
145
|
-
exports.
|
|
153
|
+
exports.deployKitePass = deployKitePass;
|
|
146
154
|
/**
|
|
147
|
-
*
|
|
155
|
+
* Configure spending rules for KitePass
|
|
156
|
+
* setSpendingRules方法会先清除现有的rules,然后设置新的rules
|
|
157
|
+
* 1. 新增/修改:需要先获取现有的rules,然后append新的rules,或者在现有rules的基础上修改
|
|
158
|
+
* 2. 删除:需要先获取现有的rules,然后删除对应的rules
|
|
159
|
+
*
|
|
160
|
+
* @param eoa - User's EOA address
|
|
161
|
+
* @param kitepassAddress - KitePass proxy address
|
|
162
|
+
* @param rules - Spending rules to configure
|
|
163
|
+
* @param signFunction - Signing function
|
|
148
164
|
*/
|
|
149
|
-
async function
|
|
150
|
-
|
|
151
|
-
const today = new Date();
|
|
152
|
-
const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
153
|
-
const todayStartTimestamp = Math.floor(todayStart.getTime() / 1000);
|
|
154
|
-
// get current week's monday 00:00:00 timestamp
|
|
155
|
-
const dayOfWeek = today.getDay(); // 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
156
|
-
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Convert Sunday=0 to Sunday=6
|
|
157
|
-
const currentWeekStartTimestamp = todayStartTimestamp - (daysFromMonday * 86400); // 86400 = seconds in a day
|
|
158
|
-
// rules fields:
|
|
159
|
-
// - TimeWindow: time window in seconds, 0 means per transaction, >0 means per time window
|
|
160
|
-
// - Budget: budget in wei, 18 decimals, like 1e18 means 1 token
|
|
161
|
-
// - InitialWindowStartTime: start time of the time window in unix timestamp
|
|
162
|
-
// should be set to the start time of the day/week/month in local time (or utc) 00:00:00
|
|
163
|
-
// - TargetProviders: target service providers array, empty array means applies to all providers
|
|
165
|
+
async function configureSpendingRules(eoa, kitepassAddress, signFunction) {
|
|
166
|
+
const sdk = initializeSDK();
|
|
164
167
|
try {
|
|
165
|
-
//
|
|
168
|
+
// Convert rules to contract format
|
|
169
|
+
const today = new Date();
|
|
170
|
+
const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
171
|
+
const todayStartTimestamp = Math.floor(todayStart.getTime() / 1000);
|
|
172
|
+
// get current week's monday 00:00:00 timestamp
|
|
173
|
+
const dayOfWeek = today.getDay(); // 0=Sunday, 1=Monday, ..., 6=Saturday
|
|
174
|
+
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Convert Sunday=0 to Sunday=6
|
|
175
|
+
const currentWeekStartTimestamp = todayStartTimestamp - (daysFromMonday * 86400); // 86400 = seconds in a day
|
|
176
|
+
// rules fields:
|
|
177
|
+
// - TimeWindow: time window in seconds, 0 means per transaction, >0 means per time window
|
|
178
|
+
// - Budget: budget in wei, 18 decimals, like 1e18 means 1 token
|
|
179
|
+
// - InitialWindowStartTime: start time of the time window in unix timestamp
|
|
180
|
+
// should be set to the start time of the day/week/month in local time (or utc) 00:00:00
|
|
181
|
+
// - TargetProviders: target service providers array, empty array means applies to all providers
|
|
166
182
|
const rulesToAdd = [
|
|
167
183
|
{
|
|
168
184
|
timeWindow: 86400n,
|
|
@@ -183,201 +199,218 @@ async function ConfigureSpendingRules(sdk, owner, proxyAddress, signFunction) {
|
|
|
183
199
|
targetProviders: [ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes('provider1'))]
|
|
184
200
|
}
|
|
185
201
|
];
|
|
202
|
+
// 获取现有的rules
|
|
203
|
+
const existingRules = await viewSpendingRules(kitepassAddress);
|
|
204
|
+
const existingRulesArray = existingRules.rules;
|
|
205
|
+
// append new rules
|
|
206
|
+
const newRules = [...existingRulesArray, ...rulesToAdd];
|
|
186
207
|
const configureRequest = {
|
|
187
|
-
target:
|
|
208
|
+
target: kitepassAddress,
|
|
188
209
|
value: 0n,
|
|
189
210
|
callData: ethers_1.ethers.Interface.from([
|
|
190
|
-
'function
|
|
191
|
-
]).encodeFunctionData('
|
|
192
|
-
[],
|
|
193
|
-
rulesToAdd // rules to add
|
|
194
|
-
])
|
|
211
|
+
'function setSpendingRules(tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders)[] calldata rules)'
|
|
212
|
+
]).encodeFunctionData('setSpendingRules', [newRules])
|
|
195
213
|
};
|
|
196
214
|
console.log('Configuring spending rules...');
|
|
197
|
-
|
|
198
|
-
console.log('- Rule 2: 10 tokens per hour (specific provider)');
|
|
199
|
-
const result = await sdk.sendUserOperationAndWait(owner, configureRequest, signFunction);
|
|
215
|
+
const result = await sdk.sendUserOperationAndWait(eoa, configureRequest, signFunction);
|
|
200
216
|
if (result.status.status === 'success') {
|
|
201
|
-
|
|
202
|
-
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
transactionHash: result.status.transactionHash
|
|
220
|
+
};
|
|
203
221
|
}
|
|
204
222
|
else {
|
|
205
|
-
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
error: result.status.reason
|
|
226
|
+
};
|
|
206
227
|
}
|
|
207
228
|
}
|
|
208
229
|
catch (error) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
});
|
|
214
|
-
// Frontend can handle different error types
|
|
215
|
-
switch (aaError.type) {
|
|
216
|
-
case 'ESTIMATE_GAS_FAILED':
|
|
217
|
-
console.log('Suggestion: Check if the contract call is valid');
|
|
218
|
-
break;
|
|
219
|
-
case 'SEND_USEROP_FAILED':
|
|
220
|
-
console.log('Suggestion: Try again or check bundler status');
|
|
221
|
-
break;
|
|
222
|
-
case 'NETWORK_ERROR':
|
|
223
|
-
console.log('Suggestion: Check your internet connection');
|
|
224
|
-
break;
|
|
225
|
-
default:
|
|
226
|
-
console.log('Suggestion: Check console for more details');
|
|
227
|
-
}
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
error: handleAAError(error)
|
|
233
|
+
};
|
|
228
234
|
}
|
|
229
235
|
}
|
|
230
|
-
exports.
|
|
236
|
+
exports.configureSpendingRules = configureSpendingRules;
|
|
231
237
|
/**
|
|
232
|
-
*
|
|
238
|
+
* View current spending rules (read-only)
|
|
239
|
+
* 查看当前消费规则(只读)
|
|
233
240
|
*/
|
|
234
|
-
async function
|
|
241
|
+
async function viewSpendingRules(kitepassAddress) {
|
|
235
242
|
try {
|
|
236
243
|
const provider = new ethers_1.ethers.JsonRpcProvider('https://rpc-testnet.gokite.ai');
|
|
237
|
-
const contract = new ethers_1.ethers.Contract(
|
|
244
|
+
const contract = new ethers_1.ethers.Contract(kitepassAddress, [
|
|
238
245
|
'function getSpendingRules() view returns (tuple(tuple(uint256 timeWindow, uint160 budget, uint96 initialWindowStartTime, bytes32[] targetProviders) rule, tuple(uint128 amountUsed, uint128 currentTimeWindowStartTime) usage)[])'
|
|
239
246
|
], provider);
|
|
240
|
-
console.log('Fetching spending rules...');
|
|
241
247
|
const spendingRules = await contract.getSpendingRules();
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
return {
|
|
249
|
+
success: true,
|
|
250
|
+
rules: spendingRules.map((rule, index) => ({
|
|
251
|
+
index,
|
|
252
|
+
timeWindow: Number(rule.rule.timeWindow),
|
|
253
|
+
budget: ethers_1.ethers.formatUnits(rule.rule.budget, 18),
|
|
254
|
+
used: ethers_1.ethers.formatUnits(rule.usage.amountUsed, 18),
|
|
255
|
+
providersCount: rule.rule.targetProviders.length
|
|
256
|
+
}))
|
|
257
|
+
};
|
|
250
258
|
}
|
|
251
259
|
catch (error) {
|
|
252
|
-
|
|
260
|
+
return {
|
|
261
|
+
success: false,
|
|
262
|
+
error: error.message
|
|
263
|
+
};
|
|
253
264
|
}
|
|
254
265
|
}
|
|
255
|
-
exports.
|
|
266
|
+
exports.viewSpendingRules = viewSpendingRules;
|
|
256
267
|
/**
|
|
257
|
-
*
|
|
268
|
+
* Withdraw funds from KitePass
|
|
269
|
+
* 从KitePass提取资金
|
|
258
270
|
*/
|
|
259
|
-
async function
|
|
271
|
+
async function withdrawFunds(eoa, kitepassAddress, amount, tokenAddress = ADDRESSES.SETTLEMENT_TOKEN, signFunction) {
|
|
272
|
+
const sdk = initializeSDK();
|
|
260
273
|
try {
|
|
261
|
-
const withdrawAmount = ethers_1.ethers.parseUnits('50', 18); // Withdraw 50 tokens
|
|
262
274
|
const withdrawRequest = {
|
|
263
|
-
target:
|
|
275
|
+
target: kitepassAddress,
|
|
264
276
|
value: 0n,
|
|
265
277
|
callData: ethers_1.ethers.Interface.from([
|
|
266
278
|
'function withdrawFunds(address token, uint256 amount)'
|
|
267
279
|
]).encodeFunctionData('withdrawFunds', [
|
|
268
|
-
|
|
269
|
-
|
|
280
|
+
tokenAddress,
|
|
281
|
+
ethers_1.ethers.parseUnits(amount, 18)
|
|
270
282
|
])
|
|
271
283
|
};
|
|
272
|
-
console.log(
|
|
273
|
-
|
|
274
|
-
console.log(`- Amount: ${ethers_1.ethers.formatUnits(withdrawAmount, 18)} tokens`);
|
|
275
|
-
const result = await sdk.sendUserOperationAndWait(owner, withdrawRequest, signFunction);
|
|
284
|
+
console.log(`Withdrawing ${amount} tokens...`);
|
|
285
|
+
const result = await sdk.sendUserOperationAndWait(eoa, withdrawRequest, signFunction);
|
|
276
286
|
if (result.status.status === 'success') {
|
|
277
|
-
|
|
278
|
-
|
|
287
|
+
return {
|
|
288
|
+
success: true,
|
|
289
|
+
transactionHash: result.status.transactionHash
|
|
290
|
+
};
|
|
279
291
|
}
|
|
280
292
|
else {
|
|
281
|
-
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
error: result.status.reason
|
|
296
|
+
};
|
|
282
297
|
}
|
|
283
298
|
}
|
|
284
299
|
catch (error) {
|
|
285
|
-
|
|
300
|
+
return {
|
|
301
|
+
success: false,
|
|
302
|
+
error: handleAAError(error)
|
|
303
|
+
};
|
|
286
304
|
}
|
|
287
305
|
}
|
|
288
|
-
exports.
|
|
306
|
+
exports.withdrawFunds = withdrawFunds;
|
|
289
307
|
/**
|
|
290
|
-
*
|
|
308
|
+
* Check token balance (read-only)
|
|
309
|
+
* 查看代币余额(只读)
|
|
291
310
|
*/
|
|
292
|
-
async function
|
|
311
|
+
async function checkTokenBalance(address, tokenAddress = ADDRESSES.SETTLEMENT_TOKEN) {
|
|
293
312
|
try {
|
|
294
313
|
const provider = new ethers_1.ethers.JsonRpcProvider('https://rpc-testnet.gokite.ai');
|
|
295
|
-
const tokenContract = new ethers_1.ethers.Contract(
|
|
314
|
+
const tokenContract = new ethers_1.ethers.Contract(tokenAddress, [
|
|
296
315
|
'function balanceOf(address account) view returns (uint256)',
|
|
297
316
|
'function symbol() view returns (string)',
|
|
298
317
|
'function decimals() view returns (uint8)'
|
|
299
318
|
], provider);
|
|
300
|
-
console.log('Checking token balance...');
|
|
301
319
|
const [balance, symbol, decimals] = await Promise.all([
|
|
302
|
-
tokenContract.balanceOf(
|
|
320
|
+
tokenContract.balanceOf(address),
|
|
303
321
|
tokenContract.symbol(),
|
|
304
322
|
tokenContract.decimals()
|
|
305
323
|
]);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
324
|
+
return {
|
|
325
|
+
success: true,
|
|
326
|
+
balance: ethers_1.ethers.formatUnits(balance, decimals),
|
|
327
|
+
symbol,
|
|
328
|
+
decimals,
|
|
329
|
+
address
|
|
330
|
+
};
|
|
310
331
|
}
|
|
311
332
|
catch (error) {
|
|
312
|
-
|
|
333
|
+
return {
|
|
334
|
+
success: false,
|
|
335
|
+
error: error.message
|
|
336
|
+
};
|
|
313
337
|
}
|
|
314
338
|
}
|
|
315
|
-
exports.
|
|
339
|
+
exports.checkTokenBalance = checkTokenBalance;
|
|
316
340
|
/**
|
|
317
|
-
*
|
|
341
|
+
* Register a service in the service registry
|
|
342
|
+
* 在服务注册表中注册服务
|
|
343
|
+
*
|
|
344
|
+
* @param eoa - Service owner's EOA address
|
|
345
|
+
* @param serviceId - Service ID (bytes32)
|
|
346
|
+
* @param serviceInfo - Service information
|
|
347
|
+
* @param signFunction - Signing function
|
|
318
348
|
*/
|
|
319
|
-
async function
|
|
320
|
-
|
|
321
|
-
// Initialize SDK
|
|
322
|
-
const sdk = new gokite_aa_sdk_1.GokiteAASDK('kite_testnet', 'https://rpc-testnet.gokite.ai', 'http://localhost:14337/rpc/');
|
|
323
|
-
const owner = '0x4A50DCA63d541372ad36E5A36F1D542d51164F19';
|
|
324
|
-
const accountAddress = sdk.getAccountAddress(owner);
|
|
325
|
-
console.log('Account address:', accountAddress);
|
|
326
|
-
// Simple ETH transfer example
|
|
327
|
-
const sendEthRequest = {
|
|
328
|
-
target: '0x4A50DCA63d541372ad36E5A36F1D542d51164F19',
|
|
329
|
-
value: ethers_1.ethers.parseEther('0.001'),
|
|
330
|
-
callData: '0x'
|
|
331
|
-
};
|
|
332
|
-
const signFunction = async (userOpHash) => {
|
|
333
|
-
const signer_pk = process.env.PRIVATE_KEY;
|
|
334
|
-
const signer = new ethers_1.ethers.Wallet(signer_pk);
|
|
335
|
-
return signer.signMessage(ethers_1.ethers.getBytes(userOpHash));
|
|
336
|
-
};
|
|
349
|
+
async function registerService(eoa, serviceId, serviceInfo, signFunction) {
|
|
350
|
+
const sdk = initializeSDK();
|
|
337
351
|
try {
|
|
338
|
-
const
|
|
352
|
+
const registerRequest = {
|
|
353
|
+
target: ADDRESSES.SERVICE_REGISTRY,
|
|
354
|
+
value: 0n,
|
|
355
|
+
callData: ethers_1.ethers.Interface.from([
|
|
356
|
+
'function registerService(bytes32 serviceId, tuple(address serviceOwner, uint8 priceModel, uint256 unitPrice, bytes32 provider, bytes metadata, string name, bool isPublic) service)'
|
|
357
|
+
]).encodeFunctionData('registerService', [
|
|
358
|
+
serviceId,
|
|
359
|
+
serviceInfo
|
|
360
|
+
])
|
|
361
|
+
};
|
|
362
|
+
console.log(`Registering service: ${serviceInfo.name}...`);
|
|
363
|
+
const result = await sdk.sendUserOperationAndWait(eoa, registerRequest, signFunction);
|
|
339
364
|
if (result.status.status === 'success') {
|
|
340
|
-
|
|
341
|
-
|
|
365
|
+
return {
|
|
366
|
+
success: true,
|
|
367
|
+
transactionHash: result.status.transactionHash,
|
|
368
|
+
serviceId
|
|
369
|
+
};
|
|
342
370
|
}
|
|
343
371
|
else {
|
|
344
|
-
|
|
372
|
+
return {
|
|
373
|
+
success: false,
|
|
374
|
+
error: result.status.reason
|
|
375
|
+
};
|
|
345
376
|
}
|
|
346
377
|
}
|
|
347
378
|
catch (error) {
|
|
348
|
-
|
|
379
|
+
return {
|
|
380
|
+
success: false,
|
|
381
|
+
error: handleAAError(error)
|
|
382
|
+
};
|
|
349
383
|
}
|
|
350
384
|
}
|
|
351
|
-
exports.
|
|
385
|
+
exports.registerService = registerService;
|
|
386
|
+
// =============================================================================
|
|
387
|
+
// UTILITY FUNCTIONS - 工具函数
|
|
388
|
+
// =============================================================================
|
|
389
|
+
/**
|
|
390
|
+
* Get TransparentUpgradeableProxy bytecode
|
|
391
|
+
* 获取透明可升级代理字节码
|
|
392
|
+
*/
|
|
393
|
+
function getTransparentProxyBytecode() {
|
|
394
|
+
return '0x60a0604052610b278038038061001481610293565b928339810160608282031261028e5761002c826102b8565b610038602084016102b8565b604084015190936001600160401b03821161028e570182601f8201121561028e5780519061006d610068836102cc565b610293565b938285526020838301011161028e5760005b828110610279575050602060009184010152803b15610258577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0383169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a281511561023f5760008083602061013595519101845af43d15610237573d91610125610068846102cc565b9283523d6000602085013e6102e7565b505b604051906104918083016001600160401b0381118482101761022157602092849261067684396001600160a01b031681520301906000f080156102155760018060a01b031680608052600080516020610b07833981519152547f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6040805160018060a01b0384168152846020820152a181156101ff576001600160a01b03191617600080516020610b078339815191525560405161032d908161034982396080518160070152f35b633173bdd160e11b600052600060045260246000fd5b6040513d6000823e3d90fd5b634e487b7160e01b600052604160045260246000fd5b6060916102e7565b505034156101375763b398979f60e01b60005260046000fd5b634c9c8ce360e01b60009081526001600160a01b0391909116600452602490fd5b8060208092840101518282880101520161007f565b600080fd5b6040519190601f01601f191682016001600160401b0381118382101761022157604052565b51906001600160a01b038216820361028e57565b6001600160401b03811161022157601f01601f191660200190565b9061030d57508051156102fc57805190602001fd5b630a12f52160e11b60005260046000fd5b8151158061033f575b61031e575090565b639996b31560e01b60009081526001600160a01b0391909116600452602490fd5b50803b1561031656fe6080604052337f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031603610069576000356001600160e01b03191663278f794360e11b1461005f576334ad5dbb60e21b60005260046000fd5b610067610113565b005b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5460009081906001600160a01b0316368280378136915af43d6000803e156100b1573d6000f35b3d6000fd5b634e487b7160e01b600052604160045260246000fd5b6040519190601f01601f1916820167ffffffffffffffff8111838210176100f257604052565b6100b6565b67ffffffffffffffff81116100f257601f01601f191660200190565b3660041161019d57604036600319011261019d576004356001600160a01b0381169081900361019d576024359067ffffffffffffffff821161019d573660238301121561019d5781600401359061017161016c836100f7565b6100cc565b91808352366024828601011161019d57602081600092602461019b970183870137840101526101a2565b565b600080fd5b90813b15610239577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0384169081179091557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a280511561021f5761021c9161025b565b50565b50503461022857565b63b398979f60e01b60005260046000fd5b50634c9c8ce360e01b60009081526001600160a01b0391909116600452602490fd5b60008061028f93602081519101845af43d15610292573d9161027f61016c846100f7565b9283523d6000602085013e610296565b90565b6060915b906102bc57508051156102ab57805190602001fd5b630a12f52160e11b60005260046000fd5b815115806102ee575b6102cd575090565b639996b31560e01b60009081526001600160a01b0391909116600452602490fd5b50803b156102c556fea2646970667358221220597147005a6fe654561cbab25a93153cc233180473a65a90bd427f0c1f41018764736f6c634300081c003360803460bc57601f61049138819003918201601f19168301916001600160401b0383118484101760c15780849260209460405283398101031260bc57516001600160a01b0381169081900360bc57801560a657600080546001600160a01b031981168317825560405192916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36103b990816100d88239f35b631e4fbdf760e01b600052600060045260246000fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe6080604052600436101561001257600080fd5b6000803560e01c8063715018a6146102875780638da5cb5b146102605780639623609d1461012f578063ad3cb1cc146100e25763f2fde38b1461005457600080fd5b346100df5760203660031901126100df576004356001600160a01b038116908190036100dd5761008261035a565b80156100c95781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b505b80fd5b50346100df57806003193601126100df575061012b6040516101056040826102e1565b60058152640352e302e360dc1b6020820152604051918291602083526020830190610319565b0390f35b5060603660031901126100df576004356001600160a01b038116908190036100dd576024356001600160a01b038116908190036102405760443567ffffffffffffffff811161025c573660238201121561025c57806004013567ffffffffffffffff8111610248576040518593929091906101b4601f8301601f1916602001846102e1565b81835236602483830101116102445781859260246020930183860137830101526101dc61035a565b833b156102405761021293839260405180968194829363278f794360e11b84526004840152604060248401526044830190610319565b039134905af18015610233576102255780f35b61022e916102e1565b388180f35b50604051903d90823e3d90fd5b8280fd5b8480fd5b634e487b7160e01b85526041600452602485fd5b8380fd5b50346100df57806003193601126100df57546040516001600160a01b039091168152602090f35b50346100df57806003193601126100df576102a061035a565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b90601f8019910116810190811067ffffffffffffffff82111761030357604052565b634e487b7160e01b600052604160045260246000fd5b919082519283825260005b848110610345575050826000602080949584010152601f8019910116010190565b80602080928401015182828601015201610324565b6000546001600160a01b0316330361036e57565b63118cdaa760e01b6000523360045260246000fdfea26469706673582212207bc32ec723f6be34b228b59aef2ef61b4e6a8eb5bc67fcdd495248566e3b6e0c64736f6c634300081c0033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103';
|
|
395
|
+
}
|
|
396
|
+
exports.getTransparentProxyBytecode = getTransparentProxyBytecode;
|
|
352
397
|
/**
|
|
353
|
-
* Parse ContractCreated event from transaction logs
|
|
354
|
-
*
|
|
398
|
+
* Parse ContractCreated event from transaction logs
|
|
399
|
+
* 从交易日志中解析合约创建事件
|
|
355
400
|
*/
|
|
356
401
|
async function parseContractCreatedEvent(transactionHash) {
|
|
357
402
|
try {
|
|
358
403
|
const provider = new ethers_1.ethers.JsonRpcProvider('https://rpc-testnet.gokite.ai');
|
|
359
404
|
const receipt = await provider.getTransactionReceipt(transactionHash);
|
|
360
|
-
if (!receipt)
|
|
361
|
-
console.error('Transaction receipt not found');
|
|
405
|
+
if (!receipt)
|
|
362
406
|
return null;
|
|
363
|
-
}
|
|
364
|
-
// ContractCreated event signature: ContractCreated(address indexed contractAddress)
|
|
365
407
|
const contractCreatedEventSignature = ethers_1.ethers.id('ContractCreated(address)');
|
|
366
|
-
console.log('Parsing transaction logs...');
|
|
367
|
-
console.log(`Total logs: ${receipt.logs.length}`);
|
|
368
408
|
for (const log of receipt.logs) {
|
|
369
|
-
// Check if this log matches the ContractCreated event
|
|
370
409
|
if (log.topics[0] === contractCreatedEventSignature) {
|
|
371
|
-
// The first topic is the event signature
|
|
372
|
-
// The second topic is the indexed contractAddress
|
|
373
410
|
const contractAddress = ethers_1.ethers.AbiCoder.defaultAbiCoder().decode(['address'], log.topics[1])[0];
|
|
374
|
-
console.log('Found ContractCreated event:');
|
|
375
|
-
console.log('- Contract Address:', contractAddress);
|
|
376
|
-
console.log('- From Address:', log.address);
|
|
377
411
|
return contractAddress;
|
|
378
412
|
}
|
|
379
413
|
}
|
|
380
|
-
console.log('No ContractCreated event found in transaction logs');
|
|
381
414
|
return null;
|
|
382
415
|
}
|
|
383
416
|
catch (error) {
|
|
@@ -385,13 +418,79 @@ async function parseContractCreatedEvent(transactionHash) {
|
|
|
385
418
|
return null;
|
|
386
419
|
}
|
|
387
420
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
421
|
+
exports.parseContractCreatedEvent = parseContractCreatedEvent;
|
|
422
|
+
// =============================================================================
|
|
423
|
+
// EXAMPLE USAGE - 使用示例
|
|
424
|
+
// =============================================================================
|
|
425
|
+
/**
|
|
426
|
+
* Example usage for frontend integration
|
|
427
|
+
* 前端集成使用示例
|
|
428
|
+
*/
|
|
429
|
+
async function exampleUsage() {
|
|
430
|
+
// Replace with actual signing function from Privy/Particle
|
|
431
|
+
// 替换为实际的Privy/Particle签名函数
|
|
432
|
+
const signFunction = async (userOpHash) => {
|
|
433
|
+
const signer_pk = process.env.PRIVATE_KEY;
|
|
434
|
+
const signer = new ethers_1.ethers.Wallet(signer_pk);
|
|
435
|
+
return signer.signMessage(ethers_1.ethers.getBytes(userOpHash));
|
|
436
|
+
};
|
|
437
|
+
const userEoa = '0x4A50DCA63d541372ad36E5A36F1D542d51164F19';
|
|
438
|
+
// actual token recipient address
|
|
439
|
+
const toAddress = '0x4A50DCA63d541372ad36E5A36F1D542d51164F19';
|
|
440
|
+
// // 1. Transfer ERC20 tokens
|
|
441
|
+
// console.log('\n=== 1. ERC20 Transfer ===');
|
|
442
|
+
// const transferResult = await transferERC20(
|
|
443
|
+
// userEoa,
|
|
444
|
+
// toAddress,
|
|
445
|
+
// '10',
|
|
446
|
+
// ADDRESSES.SETTLEMENT_TOKEN,
|
|
447
|
+
// 18,
|
|
448
|
+
// signFunction
|
|
449
|
+
// );
|
|
450
|
+
// console.log('Transfer result:', transferResult);
|
|
451
|
+
// 2. Deploy KitePass
|
|
452
|
+
console.log('\n=== 2. Deploy KitePass ===');
|
|
453
|
+
const deployResult = await deployKitePass(userEoa, signFunction);
|
|
454
|
+
console.log('Deploy result:', deployResult);
|
|
455
|
+
// if (deployResult.success && deployResult.proxyAddress) {
|
|
456
|
+
// // 3. Configure spending rules
|
|
457
|
+
// console.log('\n=== 3. Configure Spending Rules ===');
|
|
458
|
+
// const configResult = await configureSpendingRules(
|
|
459
|
+
// userEoa,
|
|
460
|
+
// deployResult.proxyAddress,
|
|
461
|
+
// signFunction
|
|
462
|
+
// );
|
|
463
|
+
// console.log('Config result:', configResult);
|
|
464
|
+
// // 4. View spending rules
|
|
465
|
+
// console.log('\n=== 4. View Spending Rules ===');
|
|
466
|
+
// const rulesResult = await viewSpendingRules(deployResult.proxyAddress);
|
|
467
|
+
// console.log('Rules result:', rulesResult);
|
|
468
|
+
// }
|
|
469
|
+
// // 5. Register service
|
|
470
|
+
// console.log('\n=== 5. Register Service ===');
|
|
471
|
+
// // serviceId, get from backend
|
|
472
|
+
// const serviceId = ethers.hexlify(randomBytes(32));
|
|
473
|
+
// const sdk = initializeSDK();
|
|
474
|
+
// const serviceOwner = sdk.getAccountAddress(userEoa);
|
|
475
|
+
// const serviceResult = await registerService(
|
|
476
|
+
// userEoa,
|
|
477
|
+
// serviceId,
|
|
478
|
+
// {
|
|
479
|
+
// serviceOwner: serviceOwner,
|
|
480
|
+
// priceModel: 0,
|
|
481
|
+
// unitPrice: 100,
|
|
482
|
+
// // provider Id, bytes32
|
|
483
|
+
// provider: ethers.hexlify(randomBytes(32)),
|
|
484
|
+
// // service metadata, currently not used
|
|
485
|
+
// metadata: '0x',
|
|
486
|
+
// name: 'Test Service',
|
|
487
|
+
// isPublic: true
|
|
488
|
+
// },
|
|
489
|
+
// signFunction
|
|
490
|
+
// );
|
|
491
|
+
// console.log('Service result:', serviceResult);
|
|
492
|
+
}
|
|
493
|
+
// Run example if executed directly
|
|
494
|
+
if (require.main === module) {
|
|
495
|
+
exampleUsage().catch(console.error);
|
|
394
496
|
}
|
|
395
|
-
// Legacy export for backwards compatibility
|
|
396
|
-
exports.example = basicExample;
|
|
397
|
-
main().catch(console.error);
|
package/dist/gokite-aa-sdk.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UserOperation, UserOperationRequest, BatchUserOperationRequest, UserOperationGasEstimate, UserOperationStatus, SignFunction, PollingOptions } from './types';
|
|
1
|
+
import { UserOperation, UserOperationRequest, BatchUserOperationRequest, UserOperationGasEstimate, UserOperationStatus, SignFunction, PollingOptions, UserOperationEstimate } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Main Gokite AA SDK class
|
|
4
4
|
*/
|
|
@@ -38,7 +38,7 @@ export declare class GokiteAASDK {
|
|
|
38
38
|
/**
|
|
39
39
|
* Create user operation
|
|
40
40
|
*/
|
|
41
|
-
createUserOperation(owner: string, request: UserOperationRequest | BatchUserOperationRequest, salt?: bigint, paymasterAddress?: string): Promise<Partial<UserOperation>>;
|
|
41
|
+
createUserOperation(owner: string, request: UserOperationRequest | BatchUserOperationRequest, salt?: bigint, paymasterAddress?: string, tokenAddress?: string): Promise<Partial<UserOperation>>;
|
|
42
42
|
/**
|
|
43
43
|
* Estimate gas for user operation
|
|
44
44
|
*/
|
|
@@ -46,7 +46,7 @@ export declare class GokiteAASDK {
|
|
|
46
46
|
/**
|
|
47
47
|
* Sign and send user operation
|
|
48
48
|
*/
|
|
49
|
-
sendUserOperation(owner: string, request: UserOperationRequest | BatchUserOperationRequest, signFn: SignFunction, salt?: bigint, paymasterAddress?: string): Promise<string>;
|
|
49
|
+
sendUserOperation(owner: string, request: UserOperationRequest | BatchUserOperationRequest, signFn: SignFunction, salt?: bigint, paymasterAddress?: string, tokenAddress?: string): Promise<string>;
|
|
50
50
|
/**
|
|
51
51
|
* Send user operation directly to EntryPoint contract (bypassing bundler)
|
|
52
52
|
* Note: This requires a signer to pay for gas. For now, we'll try with the read-only provider.
|
|
@@ -60,9 +60,30 @@ export declare class GokiteAASDK {
|
|
|
60
60
|
* Enhanced polling for user operation status with detailed result parsing
|
|
61
61
|
*/
|
|
62
62
|
pollUserOperationStatus(userOpHash: string, options?: PollingOptions): Promise<UserOperationStatus>;
|
|
63
|
+
/**
|
|
64
|
+
* Estimate user operation costs and get paymaster information
|
|
65
|
+
*/
|
|
66
|
+
estimateUserOperation(owner: string, request: UserOperationRequest | BatchUserOperationRequest): Promise<UserOperationEstimate>;
|
|
67
|
+
/**
|
|
68
|
+
* Get paymaster information including sponsorship status and supported tokens
|
|
69
|
+
*/
|
|
70
|
+
private getPaymasterInfo;
|
|
63
71
|
/**
|
|
64
72
|
* Send user operation and wait for completion with detailed status
|
|
65
73
|
*/
|
|
74
|
+
/**
|
|
75
|
+
* Send user operation with specified payment token and wait for completion
|
|
76
|
+
*/
|
|
77
|
+
sendUserOperationWithPayment(owner: string, request: UserOperationRequest | BatchUserOperationRequest, baseUserOp: Partial<UserOperation>, // base userOp from estimate
|
|
78
|
+
tokenAddress: string, // token address for payment
|
|
79
|
+
signFn: SignFunction, salt?: bigint, pollingOptions?: PollingOptions): Promise<{
|
|
80
|
+
userOpHash: string;
|
|
81
|
+
status: UserOperationStatus;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Add ERC20 approve operations to the request (approve 0 first, then max)
|
|
85
|
+
*/
|
|
86
|
+
private addApproveOperation;
|
|
66
87
|
sendUserOperationAndWait(owner: string, request: UserOperationRequest | BatchUserOperationRequest, signFn: SignFunction, salt?: bigint, paymasterAddress?: string, pollingOptions?: PollingOptions): Promise<{
|
|
67
88
|
userOpHash: string;
|
|
68
89
|
status: UserOperationStatus;
|
package/dist/gokite-aa-sdk.js
CHANGED
|
@@ -254,7 +254,7 @@ class GokiteAASDK {
|
|
|
254
254
|
/**
|
|
255
255
|
* Create user operation
|
|
256
256
|
*/
|
|
257
|
-
async createUserOperation(owner, request, salt, paymasterAddress) {
|
|
257
|
+
async createUserOperation(owner, request, salt, paymasterAddress, tokenAddress) {
|
|
258
258
|
const actualSalt = salt || (0, utils_1.generateSalt)();
|
|
259
259
|
const accountAddress = this.getAccountAddress(owner, actualSalt);
|
|
260
260
|
const isDeployed = await this.isAccountDeloyed(accountAddress);
|
|
@@ -274,9 +274,13 @@ class GokiteAASDK {
|
|
|
274
274
|
paymasterAddress = this.config.paymaster;
|
|
275
275
|
}
|
|
276
276
|
// Pack paymaster data (paymasterAddress, paymasterVerificationGasLimit, postOpGasLimit, paymasterData)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
let paymasterAndData = '0x';
|
|
278
|
+
if (paymasterAddress) {
|
|
279
|
+
// For token payments, append token address to paymaster data
|
|
280
|
+
tokenAddress = tokenAddress || this.config.supportedTokens[1].address;
|
|
281
|
+
const paymasterData = ethers_1.ethers.solidityPacked(['address'], [tokenAddress]);
|
|
282
|
+
paymasterAndData = (0, utils_1.packPaymasterAndData)(paymasterAddress, BigInt(500000), BigInt(500000), paymasterData);
|
|
283
|
+
}
|
|
280
284
|
return {
|
|
281
285
|
sender: accountAddress,
|
|
282
286
|
nonce: await this.getAccountNonce(accountAddress),
|
|
@@ -300,13 +304,14 @@ class GokiteAASDK {
|
|
|
300
304
|
/**
|
|
301
305
|
* Sign and send user operation
|
|
302
306
|
*/
|
|
303
|
-
async sendUserOperation(owner, request, signFn, salt, paymasterAddress) {
|
|
307
|
+
async sendUserOperation(owner, request, signFn, salt, paymasterAddress, tokenAddress) {
|
|
304
308
|
// Create user operation
|
|
305
|
-
const userOp = await this.createUserOperation(owner, request, salt, paymasterAddress);
|
|
309
|
+
const userOp = await this.createUserOperation(owner, request, salt, paymasterAddress, tokenAddress);
|
|
306
310
|
// Add dummy signature for gas estimation
|
|
307
311
|
const userOpWithDummy = (0, utils_1.createUserOpForEstimation)(userOp);
|
|
308
312
|
// Estimate gas using bundler
|
|
309
313
|
const gasEstimate = await this.provider.estimateUserOperationGas(userOpWithDummy, this.config.entryPoint);
|
|
314
|
+
gasEstimate.callGasLimit = gasEstimate.callGasLimit + 5000000n;
|
|
310
315
|
console.log('gasEstimate:', gasEstimate);
|
|
311
316
|
// Update gas fields in packed format (verificationGasLimit first, callGasLimit second)
|
|
312
317
|
userOp.accountGasLimits = (0, utils_1.packAccountGasLimits)(gasEstimate.verificationGasLimit, gasEstimate.callGasLimit);
|
|
@@ -409,9 +414,150 @@ class GokiteAASDK {
|
|
|
409
414
|
}
|
|
410
415
|
throw new Error(`UserOp polling timeout: ${userOpHash} (attempt ${retryCount})`);
|
|
411
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Estimate user operation costs and get paymaster information
|
|
419
|
+
*/
|
|
420
|
+
async estimateUserOperation(owner, request) {
|
|
421
|
+
console.log('Estimating UserOp costs...');
|
|
422
|
+
const paymasterAddress = this.config.paymaster;
|
|
423
|
+
try {
|
|
424
|
+
// Always estimate with paymaster for higher gas limits
|
|
425
|
+
const userOp = await this.createUserOperation(owner, request, undefined, paymasterAddress);
|
|
426
|
+
console.log('[estimateUserOperation]userOp:', userOp);
|
|
427
|
+
const gasEstimate = await this.estimateGas(userOp);
|
|
428
|
+
gasEstimate.callGasLimit = gasEstimate.callGasLimit + 5000000n;
|
|
429
|
+
gasEstimate.verificationGasLimit = gasEstimate.verificationGasLimit + 1000000n;
|
|
430
|
+
gasEstimate.preVerificationGas = gasEstimate.preVerificationGas + 1000000n;
|
|
431
|
+
gasEstimate.maxFeePerGas = BigInt(10000000);
|
|
432
|
+
// Calculate total cost in KITE
|
|
433
|
+
const totalGas = gasEstimate.callGasLimit + gasEstimate.verificationGasLimit + gasEstimate.preVerificationGas;
|
|
434
|
+
const totalCostWei = totalGas * gasEstimate.maxFeePerGas;
|
|
435
|
+
const totalCostKITE = totalCostWei.toString();
|
|
436
|
+
const totalCostKITEFormatted = ethers_1.ethers.formatEther(totalCostWei);
|
|
437
|
+
// Get paymaster info
|
|
438
|
+
const { sponsorshipAvailable, remainingSponsorships, supportedTokens } = await this.getPaymasterInfo(paymasterAddress, userOp.sender, totalCostWei);
|
|
439
|
+
return {
|
|
440
|
+
totalCostKITE,
|
|
441
|
+
totalCostKITEFormatted,
|
|
442
|
+
gasEstimate,
|
|
443
|
+
sponsorshipAvailable,
|
|
444
|
+
remainingSponsorships,
|
|
445
|
+
supportedTokens,
|
|
446
|
+
paymasterAddress,
|
|
447
|
+
userOp
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
throw new types_1.AASDKError(classifyError(error, 'estimate user operation'));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Get paymaster information including sponsorship status and supported tokens
|
|
456
|
+
*/
|
|
457
|
+
async getPaymasterInfo(paymasterAddress, userAddress, estimatedCost) {
|
|
458
|
+
const paymasterABI = [
|
|
459
|
+
'function maxSponsoredTransactions() view returns (uint256)',
|
|
460
|
+
'function userSponsorship(address user) view returns (uint256)',
|
|
461
|
+
'function supportedTokens(address token) view returns (uint256)',
|
|
462
|
+
'function maxCostPerSponsoredTransaction() view returns (uint256)'
|
|
463
|
+
];
|
|
464
|
+
const paymaster = new ethers_1.ethers.Contract(paymasterAddress, paymasterABI, this.ethersProvider);
|
|
465
|
+
// Get sponsorship info
|
|
466
|
+
const [maxSponsorships, userSponsorships, maxCostPerSponsorship] = await Promise.all([
|
|
467
|
+
paymaster.maxSponsoredTransactions(),
|
|
468
|
+
paymaster.userSponsorship(userAddress),
|
|
469
|
+
paymaster.maxCostPerSponsoredTransaction()
|
|
470
|
+
]);
|
|
471
|
+
const remainingSponsorships = Math.max(0, Number(maxSponsorships) - Number(userSponsorships));
|
|
472
|
+
const sponsorshipAvailable = remainingSponsorships > 0 && estimatedCost <= maxCostPerSponsorship;
|
|
473
|
+
// Check supported tokens from config
|
|
474
|
+
const supportedTokens = [];
|
|
475
|
+
for (const token of this.config.supportedTokens) {
|
|
476
|
+
try {
|
|
477
|
+
const exchangeRate = await paymaster.supportedTokens(token.address);
|
|
478
|
+
if (exchangeRate > 0) {
|
|
479
|
+
const tokenCost = (estimatedCost * BigInt(1e18)) / exchangeRate;
|
|
480
|
+
const formattedCost = `${ethers_1.ethers.formatUnits(tokenCost, token.decimals)} ${token.symbol}`;
|
|
481
|
+
supportedTokens.push({
|
|
482
|
+
tokenAddress: token.address,
|
|
483
|
+
tokenSymbol: token.symbol,
|
|
484
|
+
tokenDecimals: token.decimals,
|
|
485
|
+
estimatedCost: tokenCost.toString(),
|
|
486
|
+
formattedCost
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
console.warn(`Failed to check token ${token.symbol}:`, error);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
sponsorshipAvailable,
|
|
496
|
+
remainingSponsorships,
|
|
497
|
+
supportedTokens
|
|
498
|
+
};
|
|
499
|
+
}
|
|
412
500
|
/**
|
|
413
501
|
* Send user operation and wait for completion with detailed status
|
|
414
502
|
*/
|
|
503
|
+
/**
|
|
504
|
+
* Send user operation with specified payment token and wait for completion
|
|
505
|
+
*/
|
|
506
|
+
async sendUserOperationWithPayment(owner, request, baseUserOp, // base userOp from estimate
|
|
507
|
+
tokenAddress, // token address for payment
|
|
508
|
+
signFn, salt, pollingOptions) {
|
|
509
|
+
console.log('Sending UserOp with token:', tokenAddress);
|
|
510
|
+
let finalRequest = request;
|
|
511
|
+
let finalUserOp = { ...baseUserOp };
|
|
512
|
+
// If using ERC20 token (not 0x0), add approve operations
|
|
513
|
+
if (tokenAddress !== '0x0000000000000000000000000000000000000000') {
|
|
514
|
+
// Add approve operations to the request
|
|
515
|
+
finalRequest = await this.addApproveOperation(owner, request, tokenAddress, this.config.paymaster);
|
|
516
|
+
// Rebuild userOp with approve operations
|
|
517
|
+
finalUserOp = await this.createUserOperation(owner, finalRequest, salt, this.config.paymaster, tokenAddress);
|
|
518
|
+
// Copy gas estimates from base userOp
|
|
519
|
+
finalUserOp.accountGasLimits = baseUserOp.accountGasLimits;
|
|
520
|
+
finalUserOp.preVerificationGas = baseUserOp.preVerificationGas;
|
|
521
|
+
finalUserOp.gasFees = baseUserOp.gasFees;
|
|
522
|
+
}
|
|
523
|
+
// Get user operation hash
|
|
524
|
+
const userOpHash = await this.getUserOpHash(finalUserOp);
|
|
525
|
+
// Sign user operation
|
|
526
|
+
const signature = await signFn(userOpHash);
|
|
527
|
+
finalUserOp.signature = signature;
|
|
528
|
+
// Send to bundler
|
|
529
|
+
const sentUserOpHash = await this.provider.sendUserOperation(finalUserOp, this.config.entryPoint);
|
|
530
|
+
console.log(`UserOp sent: ${sentUserOpHash}`);
|
|
531
|
+
// Poll for status
|
|
532
|
+
const status = await this.pollUserOperationStatus(sentUserOpHash, pollingOptions);
|
|
533
|
+
return {
|
|
534
|
+
userOpHash: sentUserOpHash,
|
|
535
|
+
status
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Add ERC20 approve operations to the request (approve 0 first, then max)
|
|
540
|
+
*/
|
|
541
|
+
async addApproveOperation(owner, request, tokenAddress, paymasterAddress) {
|
|
542
|
+
// Create approve call data - first approve 0, then approve max
|
|
543
|
+
const approve0CallData = (0, utils_1.encodeFunctionCall)(['function approve(address,uint256) returns (bool)'], 'approve', [paymasterAddress, '0']);
|
|
544
|
+
const approveMaxCallData = (0, utils_1.encodeFunctionCall)(['function approve(address,uint256) returns (bool)'], 'approve', [paymasterAddress, ethers_1.ethers.MaxUint256.toString()]);
|
|
545
|
+
// Convert single request to batch if needed
|
|
546
|
+
if ('target' in request) {
|
|
547
|
+
return {
|
|
548
|
+
targets: [tokenAddress, tokenAddress, request.target],
|
|
549
|
+
values: [BigInt(0), BigInt(0), request.value || BigInt(0)],
|
|
550
|
+
callDatas: [approve0CallData, approveMaxCallData, request.callData]
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
return {
|
|
555
|
+
targets: [tokenAddress, tokenAddress, ...request.targets],
|
|
556
|
+
values: [BigInt(0), BigInt(0), ...(request.values || [])],
|
|
557
|
+
callDatas: [approve0CallData, approveMaxCallData, ...request.callDatas]
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
415
561
|
async sendUserOperationAndWait(owner, request, signFn, salt, paymasterAddress, pollingOptions) {
|
|
416
562
|
console.log('Sending UserOp and waiting for completion...');
|
|
417
563
|
// Step 1: Send user operation
|
package/dist/types.d.ts
CHANGED
|
@@ -41,6 +41,23 @@ export interface PollingOptions {
|
|
|
41
41
|
timeout?: number;
|
|
42
42
|
maxRetries?: number;
|
|
43
43
|
}
|
|
44
|
+
export interface PaymasterTokenCost {
|
|
45
|
+
tokenAddress: string;
|
|
46
|
+
tokenSymbol?: string;
|
|
47
|
+
tokenDecimals?: number;
|
|
48
|
+
estimatedCost: string;
|
|
49
|
+
formattedCost: string;
|
|
50
|
+
}
|
|
51
|
+
export interface UserOperationEstimate {
|
|
52
|
+
totalCostKITE: string;
|
|
53
|
+
totalCostKITEFormatted: string;
|
|
54
|
+
gasEstimate: UserOperationGasEstimate;
|
|
55
|
+
sponsorshipAvailable: boolean;
|
|
56
|
+
remainingSponsorships: number;
|
|
57
|
+
supportedTokens: PaymasterTokenCost[];
|
|
58
|
+
paymasterAddress?: string;
|
|
59
|
+
userOp: Partial<UserOperation>;
|
|
60
|
+
}
|
|
44
61
|
export interface AAError {
|
|
45
62
|
type: 'ESTIMATE_GAS_FAILED' | 'SEND_USEROP_FAILED' | 'INSUFFICIENT_FUNDS' | 'INVALID_SIGNATURE' | 'BUNDLER_ERROR' | 'NETWORK_ERROR' | 'UNKNOWN_ERROR';
|
|
46
63
|
message: string;
|
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(0);
|
|
134
134
|
}
|
|
135
135
|
exports.generateSalt = generateSalt;
|
|
136
136
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gokite-aa-sdk",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Account Abstraction SDK for Gokite",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "Simple and clean Account Abstraction SDK for Gokite",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|