@keelcodes/sdk 2.3.1 → 2.4.0
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/aa/chain-defaults.d.ts +4 -0
- package/dist/aa/chain-defaults.js +7 -1
- package/dist/aa/index.d.ts +2 -0
- package/dist/aa/index.js +2 -0
- package/dist/aa/keel-session.d.ts +103 -0
- package/dist/aa/keel-session.js +244 -1
- package/dist/aa/kernel-account.d.ts +30 -0
- package/dist/aa/kernel-account.js +122 -0
- package/dist/aa/types.d.ts +4 -0
- package/dist/aa/userop.d.ts +13 -0
- package/dist/aa/userop.js +23 -1
- package/dist/aa/x402-solana.d.ts +99 -0
- package/dist/aa/x402-solana.js +171 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +2 -2
- package/dist/wallet/index.d.ts +1 -0
- package/dist/wallet/index.js +1 -0
- package/dist/wallet/solana-dex-types.d.ts +136 -0
- package/dist/wallet/solana-dex-types.js +2 -0
- package/dist/wallet/wallet-api.d.ts +13 -0
- package/dist/wallet/wallet-api.js +18 -0
- package/package.json +1 -1
|
@@ -9,6 +9,10 @@ export declare const KEEL_SESSION_VALIDATOR: Address;
|
|
|
9
9
|
export declare const KEEL_SESSION_HOOK: Address;
|
|
10
10
|
/** KeelTokenPaymaster(三链同址;gas 垫付 + 稳定币结算) */
|
|
11
11
|
export declare const KEEL_TOKEN_PAYMASTER: Address;
|
|
12
|
+
/** Kernel v3 factory(三链同址;首笔 createAccount) */
|
|
13
|
+
export declare const KEEL_KERNEL_FACTORY: Address;
|
|
14
|
+
/** Kernel v3 root validator(ECDSA,三链同址;账户 owner 验签) */
|
|
15
|
+
export declare const KEEL_ROOT_VALIDATOR: Address;
|
|
12
16
|
export declare const KEEL_CHAIN_IDS: Record<KeelChain, number>;
|
|
13
17
|
/** 各链 USDC(BSC 为 Binance-Peg USDC,18 位) */
|
|
14
18
|
export declare const KEEL_USDC: Record<KeelChain, Address>;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// ⚠️ 均为不可变部署;链上重部署须同步本表。
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.KEEL_BUNDLER_URL = exports.KEEL_USDC_DECIMALS = exports.KEEL_USDC = exports.KEEL_CHAIN_IDS = exports.KEEL_TOKEN_PAYMASTER = exports.KEEL_SESSION_HOOK = exports.KEEL_SESSION_VALIDATOR = exports.KEEL_ENTRYPOINT_V07 = void 0;
|
|
13
|
+
exports.KEEL_BUNDLER_URL = exports.KEEL_USDC_DECIMALS = exports.KEEL_USDC = exports.KEEL_CHAIN_IDS = exports.KEEL_ROOT_VALIDATOR = exports.KEEL_KERNEL_FACTORY = exports.KEEL_TOKEN_PAYMASTER = exports.KEEL_SESSION_HOOK = exports.KEEL_SESSION_VALIDATOR = exports.KEEL_ENTRYPOINT_V07 = void 0;
|
|
14
14
|
exports.keelChainConfig = keelChainConfig;
|
|
15
15
|
exports.keelPublicClient = keelPublicClient;
|
|
16
16
|
const viem_1 = require("viem");
|
|
@@ -23,6 +23,10 @@ exports.KEEL_SESSION_VALIDATOR = '0x8BCB3F5d855F5b2a1f1398690Daaa7866992bDE7';
|
|
|
23
23
|
exports.KEEL_SESSION_HOOK = '0x466b5DC3796D44b0B63FdF2d3bC7a8Ea371a891F';
|
|
24
24
|
/** KeelTokenPaymaster(三链同址;gas 垫付 + 稳定币结算) */
|
|
25
25
|
exports.KEEL_TOKEN_PAYMASTER = '0x4500A1FbAe335F5E20Fa85C70Daa98a89faD6415';
|
|
26
|
+
/** Kernel v3 factory(三链同址;首笔 createAccount) */
|
|
27
|
+
exports.KEEL_KERNEL_FACTORY = '0xaac5D4240AF87249B3f71BC8E4A2cae074A3E419';
|
|
28
|
+
/** Kernel v3 root validator(ECDSA,三链同址;账户 owner 验签) */
|
|
29
|
+
exports.KEEL_ROOT_VALIDATOR = '0x845ADb2C711129d4f3966735eD98a9F09fC4cE57';
|
|
26
30
|
exports.KEEL_CHAIN_IDS = { base: 8453, bsc: 56, ethereum: 1 };
|
|
27
31
|
/** 各链 USDC(BSC 为 Binance-Peg USDC,18 位) */
|
|
28
32
|
exports.KEEL_USDC = {
|
|
@@ -45,6 +49,8 @@ function keelChainConfig(chain, opts = {}) {
|
|
|
45
49
|
entryPoint: exports.KEEL_ENTRYPOINT_V07,
|
|
46
50
|
rpcUrl: opts.rpcUrl,
|
|
47
51
|
kernelVersion: '0.3.1',
|
|
52
|
+
kernelFactory: exports.KEEL_KERNEL_FACTORY,
|
|
53
|
+
rootValidator: exports.KEEL_ROOT_VALIDATOR,
|
|
48
54
|
sessionValidatorCopies: [opts.sessionValidator ?? exports.KEEL_SESSION_VALIDATOR],
|
|
49
55
|
sessionHookCopies: [opts.sessionHook ?? exports.KEEL_SESSION_HOOK],
|
|
50
56
|
bundlers: [
|
package/dist/aa/index.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ export * from './nonce';
|
|
|
4
4
|
export * from './token-paymaster';
|
|
5
5
|
export * from './signer';
|
|
6
6
|
export * from './userop';
|
|
7
|
+
export * from './kernel-account';
|
|
7
8
|
export * from './keel-session';
|
|
8
9
|
export * from './x402';
|
|
10
|
+
export * from './x402-solana';
|
|
9
11
|
export * from './x402-session';
|
|
10
12
|
export * from './bundler';
|
package/dist/aa/index.js
CHANGED
|
@@ -23,7 +23,9 @@ __exportStar(require("./nonce"), exports);
|
|
|
23
23
|
__exportStar(require("./token-paymaster"), exports);
|
|
24
24
|
__exportStar(require("./signer"), exports);
|
|
25
25
|
__exportStar(require("./userop"), exports);
|
|
26
|
+
__exportStar(require("./kernel-account"), exports);
|
|
26
27
|
__exportStar(require("./keel-session"), exports);
|
|
27
28
|
__exportStar(require("./x402"), exports);
|
|
29
|
+
__exportStar(require("./x402-solana"), exports);
|
|
28
30
|
__exportStar(require("./x402-session"), exports);
|
|
29
31
|
__exportStar(require("./bundler"), exports);
|
|
@@ -24,6 +24,52 @@ export interface KeelTokenLimit {
|
|
|
24
24
|
export declare function toKeelRules(policy: SessionPolicy): KeelRule[];
|
|
25
25
|
/** `policyData = abi.encode(version, validAfter, validUntil, Rule[])` */
|
|
26
26
|
export declare function encodeKeelPolicyData(policy: SessionPolicy): Hex;
|
|
27
|
+
/** Kernel.execute selector(安装期的 selectorData,必须恰好 4 字节) */
|
|
28
|
+
export declare const KEEL_SELECTOR_DATA: Hex;
|
|
29
|
+
/** hookData flag:仅未初始化时调 onInstall */
|
|
30
|
+
export declare const KEEL_HOOK_FLAG_IF_UNINITIALIZED: Hex;
|
|
31
|
+
/** hookData flag:强制调 onInstall(同账户轮换/换 key 必须用) */
|
|
32
|
+
export declare const KEEL_HOOK_FLAG_FORCE: Hex;
|
|
33
|
+
/** `enableSession(sessionId, sessionKey, policyData)` 完整 calldata(安装期 validatorData) */
|
|
34
|
+
export declare function encodeKeelEnableData(policy: SessionPolicy): Hex;
|
|
35
|
+
/** hookData = flag(1B) ‖ abi.encode(sessionId, policyData)(Kernel 剥掉首字节后交给 hook) */
|
|
36
|
+
export declare function encodeKeelHookData(policy: SessionPolicy, flag?: Hex): Hex;
|
|
37
|
+
/** 解析 slot 对应的 hook 副本;未配置返回 address(1)(= 不挂 hook)。 */
|
|
38
|
+
export declare function resolveKeelHook(chainConfig: ChainAAConfig, slot: number): Address;
|
|
39
|
+
/** 组装 installModule 的 initData(Kernel v3.1 紧凑布局) */
|
|
40
|
+
export declare function encodeKeelValidatorInstallData(params: {
|
|
41
|
+
enableData: Hex;
|
|
42
|
+
hook: Address;
|
|
43
|
+
hookData: Hex;
|
|
44
|
+
selectorData?: Hex;
|
|
45
|
+
}): Hex;
|
|
46
|
+
export interface EncodeKeelEnableSessionCallParams {
|
|
47
|
+
accountAddress: Address;
|
|
48
|
+
policy: SessionPolicy;
|
|
49
|
+
chainConfig: ChainAAConfig;
|
|
50
|
+
/** slot 序号(= validator 副本下标;一 key 一副本) */
|
|
51
|
+
slot: number;
|
|
52
|
+
/** 同账户换 key / 轮换必须传 {@link KEEL_HOOK_FLAG_FORCE} */
|
|
53
|
+
hookFlag?: Hex;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* owner 侧「安装/授权」callData:
|
|
57
|
+
* execute( installModule(VALIDATOR, validatorCopy, initData) )
|
|
58
|
+
* 作为 UserOp.callData 由 owner 签名后提交(root nonce;账户未部署时同笔带 factory)。
|
|
59
|
+
*/
|
|
60
|
+
export declare function encodeKeelEnableSessionCall(p: EncodeKeelEnableSessionCallParams): Hex;
|
|
61
|
+
export interface EncodeKeelUninstallCallParams {
|
|
62
|
+
accountAddress: Address;
|
|
63
|
+
chainConfig: ChainAAConfig;
|
|
64
|
+
slot: number;
|
|
65
|
+
/** 该 slot 的 sessionId(hook 段 onUninstall 必需) */
|
|
66
|
+
sessionId: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* owner 侧「撤销」callData(validator + hook 两段批量;无 hook 时单段)。
|
|
70
|
+
* 走 root nonce,owner 裸签 getUserOpHash;**不需要** invalidateNonce。
|
|
71
|
+
*/
|
|
72
|
+
export declare function encodeKeelUninstallCall(p: EncodeKeelUninstallCallParams): Hex;
|
|
27
73
|
/** 解析 slot 对应的 validator 副本地址(缺配置直接抛错,不回退旧模块)。 */
|
|
28
74
|
export declare function resolveKeelValidator(chainConfig: ChainAAConfig, slot: number): Address;
|
|
29
75
|
/** agent 侧签名对象:digest = keccak256(userOpHash ‖ keccak256(policyData)) */
|
|
@@ -68,3 +114,60 @@ export interface BuildKeelSessionUserOpParams {
|
|
|
68
114
|
* 与 aa-sdk 的 `buildKeelSessionUserOp` 逐字节一致(同输入同输出)。
|
|
69
115
|
*/
|
|
70
116
|
export declare function buildKeelSessionUserOp(p: BuildKeelSessionUserOpParams): Promise<UserOperationV7>;
|
|
117
|
+
export type UserOpGasOverrides = Partial<Pick<UserOperationV7, 'callGasLimit' | 'verificationGasLimit' | 'preVerificationGas' | 'maxFeePerGas' | 'maxPriorityFeePerGas'>>;
|
|
118
|
+
/** owner 侧草稿:op 未签名;owner 对 `userOpHash` 签名后填入 `op.signature`。 */
|
|
119
|
+
export interface OwnerUserOpDraft {
|
|
120
|
+
account: Address;
|
|
121
|
+
op: UserOperationV7;
|
|
122
|
+
/** owner 需签名的摘要(= EntryPoint.getUserOpHash) */
|
|
123
|
+
userOpHash: Hex;
|
|
124
|
+
}
|
|
125
|
+
export interface BuildOwnerEnableUserOpParams {
|
|
126
|
+
/** 未传 nonce(且 deploy 需派生地址)时必须提供 */
|
|
127
|
+
client?: PublicClient;
|
|
128
|
+
chainConfig: ChainAAConfig;
|
|
129
|
+
/** owner EOA(root validator 的 owner) */
|
|
130
|
+
owner: Address;
|
|
131
|
+
/** 账户地址;`deploy` 时可省略(用 factory.getAddress 反事实派生) */
|
|
132
|
+
account?: Address;
|
|
133
|
+
/** relay `POST /v1/wallet/session` 返回的 enableCallData(优先) */
|
|
134
|
+
enableCallData?: Hex;
|
|
135
|
+
/** 或本地由 policy 编码(与 enableCallData 二选一) */
|
|
136
|
+
policy?: SessionPolicy;
|
|
137
|
+
/** validator 副本下标 */
|
|
138
|
+
slot: number;
|
|
139
|
+
/** 同账户换 key / 轮换传 {@link KEEL_HOOK_FLAG_FORCE} */
|
|
140
|
+
hookFlag?: Hex;
|
|
141
|
+
/** 账户未部署时传 true(salt=0)或 `{ salt }`:自动带 factory + factoryData */
|
|
142
|
+
deploy?: boolean | {
|
|
143
|
+
salt?: bigint;
|
|
144
|
+
};
|
|
145
|
+
nonce?: bigint;
|
|
146
|
+
gas?: UserOpGasOverrides;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 首笔 / 后续 owner 授权(enable session)UserOp 构造(未签名):
|
|
150
|
+
* callData = enableCallData ?? execute(installModule(KeelSessionValidator, ...))
|
|
151
|
+
* factory = 仅在 `deploy` 时携带(relay **不会**代部署)
|
|
152
|
+
* nonce = root `getNonce(account, 0)`
|
|
153
|
+
* digest = getUserOpHash(op) → owner 裸签
|
|
154
|
+
*/
|
|
155
|
+
export declare function buildOwnerEnableUserOp(p: BuildOwnerEnableUserOpParams): Promise<OwnerUserOpDraft>;
|
|
156
|
+
export interface BuildOwnerRevokeUserOpParams {
|
|
157
|
+
/** 未传 nonce 时必须提供 */
|
|
158
|
+
client?: PublicClient;
|
|
159
|
+
chainConfig: ChainAAConfig;
|
|
160
|
+
account: Address;
|
|
161
|
+
slot: number;
|
|
162
|
+
sessionId: string;
|
|
163
|
+
nonce?: bigint;
|
|
164
|
+
gas?: UserOpGasOverrides;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* owner 撤销(revoke)UserOp 构造(未签名):
|
|
168
|
+
* callData = executeBatch[uninstallModule(VALIDATOR), uninstallModule(HOOK, sessionId)]
|
|
169
|
+
* nonce = root `getNonce(account, 0)`
|
|
170
|
+
* digest = getUserOpHash(op) → owner 裸签
|
|
171
|
+
* 上链成功后 policy 随 hook.onUninstall 一并清除,**不需要** invalidateNonce。
|
|
172
|
+
*/
|
|
173
|
+
export declare function buildOwnerRevokeUserOp(p: BuildOwnerRevokeUserOpParams): Promise<OwnerUserOpDraft>;
|
package/dist/aa/keel-session.js
CHANGED
|
@@ -9,17 +9,26 @@
|
|
|
9
9
|
// 链上只存 keccak256(policyData) 作为 commitment,因此这里必须逐字节一致。
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.KEEL_POLICY_VERSION = void 0;
|
|
12
|
+
exports.KEEL_HOOK_FLAG_FORCE = exports.KEEL_HOOK_FLAG_IF_UNINITIALIZED = exports.KEEL_SELECTOR_DATA = exports.KEEL_POLICY_VERSION = void 0;
|
|
13
13
|
exports.toBytes32 = toBytes32;
|
|
14
14
|
exports.toKeelRules = toKeelRules;
|
|
15
15
|
exports.encodeKeelPolicyData = encodeKeelPolicyData;
|
|
16
|
+
exports.encodeKeelEnableData = encodeKeelEnableData;
|
|
17
|
+
exports.encodeKeelHookData = encodeKeelHookData;
|
|
18
|
+
exports.resolveKeelHook = resolveKeelHook;
|
|
19
|
+
exports.encodeKeelValidatorInstallData = encodeKeelValidatorInstallData;
|
|
20
|
+
exports.encodeKeelEnableSessionCall = encodeKeelEnableSessionCall;
|
|
21
|
+
exports.encodeKeelUninstallCall = encodeKeelUninstallCall;
|
|
16
22
|
exports.resolveKeelValidator = resolveKeelValidator;
|
|
17
23
|
exports.keelSessionDigest = keelSessionDigest;
|
|
18
24
|
exports.encodeKeelSessionSignature = encodeKeelSessionSignature;
|
|
19
25
|
exports.buildKeelSessionUserOp = buildKeelSessionUserOp;
|
|
26
|
+
exports.buildOwnerEnableUserOp = buildOwnerEnableUserOp;
|
|
27
|
+
exports.buildOwnerRevokeUserOp = buildOwnerRevokeUserOp;
|
|
20
28
|
const viem_1 = require("viem");
|
|
21
29
|
const userop_1 = require("./userop");
|
|
22
30
|
const nonce_1 = require("./nonce");
|
|
31
|
+
const kernel_account_1 = require("./kernel-account");
|
|
23
32
|
exports.KEEL_POLICY_VERSION = 1n;
|
|
24
33
|
/** bytes32 归一化(已是 0x+64 hex 直接用,否则左对齐补零) */
|
|
25
34
|
function toBytes32(id) {
|
|
@@ -73,6 +82,140 @@ function encodeKeelPolicyData(policy) {
|
|
|
73
82
|
{ type: 'tuple[]', components: KeelRuleTuple },
|
|
74
83
|
], [exports.KEEL_POLICY_VERSION, policy.validAfter, policy.validUntil, toKeelRules(policy)]);
|
|
75
84
|
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// owner 侧编码(session 授权安装 / 撤销卸载)
|
|
87
|
+
//
|
|
88
|
+
// 安装 initData(Kernel v3.1 紧凑布局):
|
|
89
|
+
// initData = hook(20B) ‖ abi.encode(bytes validatorData, bytes hookData, bytes selectorData)
|
|
90
|
+
// validatorData = enableSession(sessionId, sessionKey, policyData) 完整 calldata
|
|
91
|
+
// hookData = flag(1B) ‖ abi.encode(bytes32 sessionId, bytes policyData)
|
|
92
|
+
// selectorData = 0xe9ae5c53(Kernel.execute,恰好 4 字节;长度≠4 时内核静默不 set)
|
|
93
|
+
// ⚠️ flag:0x00 = 仅未初始化时调 onInstall;0xff = 强制(同账户换 key 必须用 0xff)。
|
|
94
|
+
//
|
|
95
|
+
// 撤销 = 一次批量 execute:
|
|
96
|
+
// ① uninstallModule(VALIDATOR=1, validatorCopy, '0x')
|
|
97
|
+
// ② uninstallModule(HOOK=4, hookCopy, abi.encode(bytes32 sessionId))
|
|
98
|
+
// 只发①不发②会在 hook 残留 session(union 语义 ⇒ 旧更宽策略仍参与准入)。
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
/** Kernel.execute selector(安装期的 selectorData,必须恰好 4 字节) */
|
|
101
|
+
exports.KEEL_SELECTOR_DATA = '0xe9ae5c53';
|
|
102
|
+
/** hookData flag:仅未初始化时调 onInstall */
|
|
103
|
+
exports.KEEL_HOOK_FLAG_IF_UNINITIALIZED = '0x00';
|
|
104
|
+
/** hookData flag:强制调 onInstall(同账户轮换/换 key 必须用) */
|
|
105
|
+
exports.KEEL_HOOK_FLAG_FORCE = '0xff';
|
|
106
|
+
const MODULE_TYPE_VALIDATOR = 1n;
|
|
107
|
+
const MODULE_TYPE_HOOK = 4n;
|
|
108
|
+
/** address(1) = Kernel 约定「不挂 hook」 */
|
|
109
|
+
const HOOK_NONE = '0x0000000000000000000000000000000000000001';
|
|
110
|
+
/** KeelSessionValidator.enableSession(bytes32,address,bytes) */
|
|
111
|
+
const KeelSessionValidatorAbi = [
|
|
112
|
+
{
|
|
113
|
+
type: 'function',
|
|
114
|
+
name: 'enableSession',
|
|
115
|
+
inputs: [
|
|
116
|
+
{ name: 'sessionId', type: 'bytes32' },
|
|
117
|
+
{ name: 'sessionKey', type: 'address' },
|
|
118
|
+
{ name: 'policyData', type: 'bytes' },
|
|
119
|
+
],
|
|
120
|
+
outputs: [],
|
|
121
|
+
stateMutability: 'nonpayable',
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
/** Kernel 账户自身的 ERC-7579 模块管理方法 */
|
|
125
|
+
const KeelModuleManagerAbi = [
|
|
126
|
+
{
|
|
127
|
+
type: 'function',
|
|
128
|
+
name: 'installModule',
|
|
129
|
+
inputs: [
|
|
130
|
+
{ name: 'moduleTypeId', type: 'uint256' },
|
|
131
|
+
{ name: 'module', type: 'address' },
|
|
132
|
+
{ name: 'initData', type: 'bytes' },
|
|
133
|
+
],
|
|
134
|
+
outputs: [],
|
|
135
|
+
stateMutability: 'nonpayable',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'function',
|
|
139
|
+
name: 'uninstallModule',
|
|
140
|
+
inputs: [
|
|
141
|
+
{ name: 'moduleTypeId', type: 'uint256' },
|
|
142
|
+
{ name: 'module', type: 'address' },
|
|
143
|
+
{ name: 'deInitData', type: 'bytes' },
|
|
144
|
+
],
|
|
145
|
+
outputs: [],
|
|
146
|
+
stateMutability: 'nonpayable',
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
/** `enableSession(sessionId, sessionKey, policyData)` 完整 calldata(安装期 validatorData) */
|
|
150
|
+
function encodeKeelEnableData(policy) {
|
|
151
|
+
return (0, viem_1.encodeFunctionData)({
|
|
152
|
+
abi: KeelSessionValidatorAbi,
|
|
153
|
+
functionName: 'enableSession',
|
|
154
|
+
args: [toBytes32(policy.sessionId), policy.signer, encodeKeelPolicyData(policy)],
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** hookData = flag(1B) ‖ abi.encode(sessionId, policyData)(Kernel 剥掉首字节后交给 hook) */
|
|
158
|
+
function encodeKeelHookData(policy, flag = exports.KEEL_HOOK_FLAG_IF_UNINITIALIZED) {
|
|
159
|
+
return (0, viem_1.concatHex)([
|
|
160
|
+
flag,
|
|
161
|
+
(0, viem_1.encodeAbiParameters)([{ type: 'bytes32' }, { type: 'bytes' }], [toBytes32(policy.sessionId), encodeKeelPolicyData(policy)]),
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
/** 解析 slot 对应的 hook 副本;未配置返回 address(1)(= 不挂 hook)。 */
|
|
165
|
+
function resolveKeelHook(chainConfig, slot) {
|
|
166
|
+
return chainConfig.sessionHookCopies?.[slot] ?? HOOK_NONE;
|
|
167
|
+
}
|
|
168
|
+
/** 组装 installModule 的 initData(Kernel v3.1 紧凑布局) */
|
|
169
|
+
function encodeKeelValidatorInstallData(params) {
|
|
170
|
+
return (0, viem_1.concatHex)([
|
|
171
|
+
params.hook,
|
|
172
|
+
(0, viem_1.encodeAbiParameters)([{ type: 'bytes' }, { type: 'bytes' }, { type: 'bytes' }], [params.enableData, params.hookData, params.selectorData ?? exports.KEEL_SELECTOR_DATA]),
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* owner 侧「安装/授权」callData:
|
|
177
|
+
* execute( installModule(VALIDATOR, validatorCopy, initData) )
|
|
178
|
+
* 作为 UserOp.callData 由 owner 签名后提交(root nonce;账户未部署时同笔带 factory)。
|
|
179
|
+
*/
|
|
180
|
+
function encodeKeelEnableSessionCall(p) {
|
|
181
|
+
const validator = resolveKeelValidator(p.chainConfig, p.slot);
|
|
182
|
+
const hook = resolveKeelHook(p.chainConfig, p.slot);
|
|
183
|
+
const initData = encodeKeelValidatorInstallData({
|
|
184
|
+
enableData: encodeKeelEnableData(p.policy),
|
|
185
|
+
hook,
|
|
186
|
+
hookData: encodeKeelHookData(p.policy, p.hookFlag),
|
|
187
|
+
});
|
|
188
|
+
return (0, userop_1.encodeExecute)(p.accountAddress, 0n, (0, viem_1.encodeFunctionData)({
|
|
189
|
+
abi: KeelModuleManagerAbi,
|
|
190
|
+
functionName: 'installModule',
|
|
191
|
+
args: [MODULE_TYPE_VALIDATOR, validator, initData],
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* owner 侧「撤销」callData(validator + hook 两段批量;无 hook 时单段)。
|
|
196
|
+
* 走 root nonce,owner 裸签 getUserOpHash;**不需要** invalidateNonce。
|
|
197
|
+
*/
|
|
198
|
+
function encodeKeelUninstallCall(p) {
|
|
199
|
+
const validator = resolveKeelValidator(p.chainConfig, p.slot);
|
|
200
|
+
const hook = resolveKeelHook(p.chainConfig, p.slot);
|
|
201
|
+
const uninstallValidator = (0, viem_1.encodeFunctionData)({
|
|
202
|
+
abi: KeelModuleManagerAbi,
|
|
203
|
+
functionName: 'uninstallModule',
|
|
204
|
+
args: [MODULE_TYPE_VALIDATOR, validator, '0x'],
|
|
205
|
+
});
|
|
206
|
+
if (hook === HOOK_NONE) {
|
|
207
|
+
return (0, userop_1.encodeExecute)(p.accountAddress, 0n, uninstallValidator);
|
|
208
|
+
}
|
|
209
|
+
const uninstallHook = (0, viem_1.encodeFunctionData)({
|
|
210
|
+
abi: KeelModuleManagerAbi,
|
|
211
|
+
functionName: 'uninstallModule',
|
|
212
|
+
args: [MODULE_TYPE_HOOK, hook, (0, viem_1.encodeAbiParameters)([{ type: 'bytes32' }], [toBytes32(p.sessionId)])],
|
|
213
|
+
});
|
|
214
|
+
return (0, userop_1.encodeExecuteBatch)([
|
|
215
|
+
{ target: p.accountAddress, data: uninstallValidator },
|
|
216
|
+
{ target: p.accountAddress, data: uninstallHook },
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
76
219
|
/** 解析 slot 对应的 validator 副本地址(缺配置直接抛错,不回退旧模块)。 */
|
|
77
220
|
function resolveKeelValidator(chainConfig, slot) {
|
|
78
221
|
if (!Number.isInteger(slot) || slot < 0) {
|
|
@@ -130,3 +273,103 @@ async function buildKeelSessionUserOp(p) {
|
|
|
130
273
|
const sig = await p.agentSigner.signUserOp(keelSessionDigest(userOpHash, policyData));
|
|
131
274
|
return { ...op, signature: encodeKeelSessionSignature({ sessionId: p.policy.sessionId, policyData, sig }) };
|
|
132
275
|
}
|
|
276
|
+
/** root 通道 nonce:key=0 ⇒ mode=DEFAULT / vType=ROOT。 */
|
|
277
|
+
async function readRootNonce(client, entryPoint, account) {
|
|
278
|
+
return (await client.readContract({
|
|
279
|
+
address: entryPoint,
|
|
280
|
+
abi: ENTRYPOINT_GETNONCE_ABI,
|
|
281
|
+
functionName: 'getNonce',
|
|
282
|
+
args: [account, 0n],
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 首笔 / 后续 owner 授权(enable session)UserOp 构造(未签名):
|
|
287
|
+
* callData = enableCallData ?? execute(installModule(KeelSessionValidator, ...))
|
|
288
|
+
* factory = 仅在 `deploy` 时携带(relay **不会**代部署)
|
|
289
|
+
* nonce = root `getNonce(account, 0)`
|
|
290
|
+
* digest = getUserOpHash(op) → owner 裸签
|
|
291
|
+
*/
|
|
292
|
+
async function buildOwnerEnableUserOp(p) {
|
|
293
|
+
if (!p.enableCallData && !p.policy) {
|
|
294
|
+
throw new Error('[keel-sdk] buildOwnerEnableUserOp: enableCallData 与 policy 至少提供一个');
|
|
295
|
+
}
|
|
296
|
+
const deploy = p.deploy === true ? {} : p.deploy;
|
|
297
|
+
let factory;
|
|
298
|
+
let factoryData;
|
|
299
|
+
let account = p.account;
|
|
300
|
+
if (deploy) {
|
|
301
|
+
const built = (0, kernel_account_1.encodeKernelFactoryData)(p.chainConfig, p.owner, deploy.salt ?? 0n);
|
|
302
|
+
factory = built.factory;
|
|
303
|
+
factoryData = built.factoryData;
|
|
304
|
+
if (!account) {
|
|
305
|
+
if (!p.client) {
|
|
306
|
+
throw new Error('[keel-sdk] buildOwnerEnableUserOp: deploy 且未传 account 时必须提供 client(用于反事实地址预测)');
|
|
307
|
+
}
|
|
308
|
+
account = await (0, kernel_account_1.predictAccountAddress)({
|
|
309
|
+
client: p.client,
|
|
310
|
+
chainConfig: p.chainConfig,
|
|
311
|
+
owner: p.owner,
|
|
312
|
+
salt: deploy.salt ?? 0n,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!account) {
|
|
317
|
+
throw new Error('[keel-sdk] buildOwnerEnableUserOp: account 必填(首笔部署请传 deploy: true)');
|
|
318
|
+
}
|
|
319
|
+
const callData = p.enableCallData ??
|
|
320
|
+
encodeKeelEnableSessionCall({
|
|
321
|
+
accountAddress: account,
|
|
322
|
+
policy: p.policy,
|
|
323
|
+
chainConfig: p.chainConfig,
|
|
324
|
+
slot: p.slot,
|
|
325
|
+
hookFlag: p.hookFlag,
|
|
326
|
+
});
|
|
327
|
+
let nonce = p.nonce;
|
|
328
|
+
if (nonce === undefined) {
|
|
329
|
+
if (!p.client) {
|
|
330
|
+
throw new Error('[keel-sdk] buildOwnerEnableUserOp: 未传 nonce 时必须提供 client');
|
|
331
|
+
}
|
|
332
|
+
nonce = await readRootNonce(p.client, p.chainConfig.entryPoint, account);
|
|
333
|
+
}
|
|
334
|
+
const op = (0, userop_1.buildUserOpWithCallData)({
|
|
335
|
+
sender: account,
|
|
336
|
+
nonce,
|
|
337
|
+
callData,
|
|
338
|
+
factory,
|
|
339
|
+
factoryData,
|
|
340
|
+
gas: p.gas,
|
|
341
|
+
});
|
|
342
|
+
return {
|
|
343
|
+
account,
|
|
344
|
+
op,
|
|
345
|
+
userOpHash: (0, userop_1.getUserOpHash)(op, p.chainConfig.entryPoint, p.chainConfig.chainId),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* owner 撤销(revoke)UserOp 构造(未签名):
|
|
350
|
+
* callData = executeBatch[uninstallModule(VALIDATOR), uninstallModule(HOOK, sessionId)]
|
|
351
|
+
* nonce = root `getNonce(account, 0)`
|
|
352
|
+
* digest = getUserOpHash(op) → owner 裸签
|
|
353
|
+
* 上链成功后 policy 随 hook.onUninstall 一并清除,**不需要** invalidateNonce。
|
|
354
|
+
*/
|
|
355
|
+
async function buildOwnerRevokeUserOp(p) {
|
|
356
|
+
const callData = encodeKeelUninstallCall({
|
|
357
|
+
accountAddress: p.account,
|
|
358
|
+
chainConfig: p.chainConfig,
|
|
359
|
+
slot: p.slot,
|
|
360
|
+
sessionId: p.sessionId,
|
|
361
|
+
});
|
|
362
|
+
let nonce = p.nonce;
|
|
363
|
+
if (nonce === undefined) {
|
|
364
|
+
if (!p.client) {
|
|
365
|
+
throw new Error('[keel-sdk] buildOwnerRevokeUserOp: 未传 nonce 时必须提供 client');
|
|
366
|
+
}
|
|
367
|
+
nonce = await readRootNonce(p.client, p.chainConfig.entryPoint, p.account);
|
|
368
|
+
}
|
|
369
|
+
const op = (0, userop_1.buildUserOpWithCallData)({ sender: p.account, nonce, callData, gas: p.gas });
|
|
370
|
+
return {
|
|
371
|
+
account: p.account,
|
|
372
|
+
op,
|
|
373
|
+
userOpHash: (0, userop_1.getUserOpHash)(op, p.chainConfig.entryPoint, p.chainConfig.chainId),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Address, type Hex, type PublicClient } from 'viem';
|
|
2
|
+
import type { ChainAAConfig } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* 编码 Kernel v3.1 `initialize(...)` calldata。
|
|
5
|
+
* rootValidator = 0x01 ‖ rootValidator 地址(21B);validatorData = owner(20B)。
|
|
6
|
+
*/
|
|
7
|
+
export declare function encodeKernelInitialize(chainConfig: ChainAAConfig, owner: Address, rootValidator?: Address): Hex;
|
|
8
|
+
/**
|
|
9
|
+
* 首笔部署参数:`factory` + `factoryData = createAccount(initializeData, salt)`。
|
|
10
|
+
* salt 缺省 0(与 relay 的账户派生一致)。
|
|
11
|
+
*/
|
|
12
|
+
export declare function encodeKernelFactoryData(chainConfig: ChainAAConfig, owner: Address, salt?: bigint): {
|
|
13
|
+
factory: Address;
|
|
14
|
+
factoryData: Hex;
|
|
15
|
+
};
|
|
16
|
+
/** 从 createAccount 编码中解包 (data, salt) —— 供 factory.getAddress 预测复用。 */
|
|
17
|
+
export declare function unpackFactoryData(factoryData: Hex): {
|
|
18
|
+
data: Hex;
|
|
19
|
+
salt: Hex;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* 反事实地址预测:`factory.getAddress(data, salt)` view(纯 CREATE2,无合约创建)。
|
|
23
|
+
* 不依赖 `EntryPoint.getSenderAddress`(部分 RPC 拒绝 eth_call 内的 CREATE2 模拟)。
|
|
24
|
+
*/
|
|
25
|
+
export declare function predictAccountAddress(params: {
|
|
26
|
+
client: PublicClient;
|
|
27
|
+
chainConfig: ChainAAConfig;
|
|
28
|
+
owner: Address;
|
|
29
|
+
salt?: bigint;
|
|
30
|
+
}): Promise<Address>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Kernel v3 账户:factory initData 编码 / 反事实地址预测
|
|
4
|
+
//
|
|
5
|
+
// 首笔 owner 操作(enable session)之前账户通常尚未部署,UserOp 必须携带
|
|
6
|
+
// factory + factoryData,由 EntryPoint 在验证阶段 CREATE2 部署。
|
|
7
|
+
//
|
|
8
|
+
// initializeData = initialize(
|
|
9
|
+
// rootValidator, // bytes21 = 0x01 ‖ ECDSA validator(20B)
|
|
10
|
+
// hook, // address(0) → Kernel 内部归一为 address(1)(不挂 hook)
|
|
11
|
+
// validatorData, // owner EOA(20B)
|
|
12
|
+
// hookData, // '0x'
|
|
13
|
+
// initConfig // []
|
|
14
|
+
// ) // Kernel v3.1+,selector 0x3c3b752b
|
|
15
|
+
// factoryData = KernelFactory.createAccount(initializeData, bytes32 salt)
|
|
16
|
+
//
|
|
17
|
+
// ⚠️ 仅支持 Kernel v3.1+(0.3.0-beta 的 initialize 为 4 参数,Keel session 路径不支持)。
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.encodeKernelInitialize = encodeKernelInitialize;
|
|
21
|
+
exports.encodeKernelFactoryData = encodeKernelFactoryData;
|
|
22
|
+
exports.unpackFactoryData = unpackFactoryData;
|
|
23
|
+
exports.predictAccountAddress = predictAccountAddress;
|
|
24
|
+
const viem_1 = require("viem");
|
|
25
|
+
const chain_defaults_1 = require("./chain-defaults");
|
|
26
|
+
/** ValidationId 前缀:VALIDATOR 类型(0x01) */
|
|
27
|
+
const VALIDATOR_TYPE_VALIDATOR = '0x01';
|
|
28
|
+
/** Kernel v3.1+:initialize(bytes21, address, bytes, bytes, bytes[]) */
|
|
29
|
+
const KernelV3Init5Abi = [
|
|
30
|
+
{
|
|
31
|
+
type: 'function',
|
|
32
|
+
name: 'initialize',
|
|
33
|
+
inputs: [
|
|
34
|
+
{ name: '_rootValidator', type: 'bytes21', internalType: 'ValidationId' },
|
|
35
|
+
{ name: 'hook', type: 'address', internalType: 'contract IHook' },
|
|
36
|
+
{ name: 'validatorData', type: 'bytes', internalType: 'bytes' },
|
|
37
|
+
{ name: 'hookData', type: 'bytes', internalType: 'bytes' },
|
|
38
|
+
{ name: 'initConfig', type: 'bytes[]', internalType: 'bytes[]' },
|
|
39
|
+
],
|
|
40
|
+
outputs: [],
|
|
41
|
+
stateMutability: 'nonpayable',
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
/** KernelFactory.createAccount(bytes data, bytes32 salt) */
|
|
45
|
+
const KernelFactoryAbi = [
|
|
46
|
+
{
|
|
47
|
+
type: 'function',
|
|
48
|
+
name: 'createAccount',
|
|
49
|
+
inputs: [
|
|
50
|
+
{ name: 'data', type: 'bytes' },
|
|
51
|
+
{ name: 'salt', type: 'bytes32' },
|
|
52
|
+
],
|
|
53
|
+
outputs: [{ name: '', type: 'address' }],
|
|
54
|
+
stateMutability: 'payable',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
/** KernelFactory.getAddress(bytes data, bytes32 salt)(纯 CREATE2 计算) */
|
|
58
|
+
const KernelFactoryGetAddressAbi = [
|
|
59
|
+
{
|
|
60
|
+
type: 'function',
|
|
61
|
+
name: 'getAddress',
|
|
62
|
+
inputs: [
|
|
63
|
+
{ name: 'data', type: 'bytes' },
|
|
64
|
+
{ name: 'salt', type: 'bytes32' },
|
|
65
|
+
],
|
|
66
|
+
outputs: [{ name: '', type: 'address' }],
|
|
67
|
+
stateMutability: 'view',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* 编码 Kernel v3.1 `initialize(...)` calldata。
|
|
72
|
+
* rootValidator = 0x01 ‖ rootValidator 地址(21B);validatorData = owner(20B)。
|
|
73
|
+
*/
|
|
74
|
+
function encodeKernelInitialize(chainConfig, owner, rootValidator = chainConfig.rootValidator ?? chain_defaults_1.KEEL_ROOT_VALIDATOR) {
|
|
75
|
+
if (chainConfig.kernelVersion && chainConfig.kernelVersion !== '0.3.1') {
|
|
76
|
+
throw new Error(`[keel-sdk] encodeKernelInitialize 仅支持 Kernel 0.3.1+,got ${chainConfig.kernelVersion}`);
|
|
77
|
+
}
|
|
78
|
+
return (0, viem_1.encodeFunctionData)({
|
|
79
|
+
abi: KernelV3Init5Abi,
|
|
80
|
+
functionName: 'initialize',
|
|
81
|
+
args: [(0, viem_1.concatHex)([VALIDATOR_TYPE_VALIDATOR, rootValidator]), viem_1.zeroAddress, owner, '0x', []],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 首笔部署参数:`factory` + `factoryData = createAccount(initializeData, salt)`。
|
|
86
|
+
* salt 缺省 0(与 relay 的账户派生一致)。
|
|
87
|
+
*/
|
|
88
|
+
function encodeKernelFactoryData(chainConfig, owner, salt = 0n) {
|
|
89
|
+
const factory = chainConfig.kernelFactory;
|
|
90
|
+
if (!factory) {
|
|
91
|
+
throw new Error(`[keel-sdk] missing kernelFactory for chain ${chainConfig.chainId}`);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
factory,
|
|
95
|
+
factoryData: (0, viem_1.encodeFunctionData)({
|
|
96
|
+
abi: KernelFactoryAbi,
|
|
97
|
+
functionName: 'createAccount',
|
|
98
|
+
args: [encodeKernelInitialize(chainConfig, owner), (0, viem_1.toHex)(salt, { size: 32 })],
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** 从 createAccount 编码中解包 (data, salt) —— 供 factory.getAddress 预测复用。 */
|
|
103
|
+
function unpackFactoryData(factoryData) {
|
|
104
|
+
const body = factoryData.slice(10);
|
|
105
|
+
const salt = ('0x' + body.slice(64, 128));
|
|
106
|
+
const dataLen = parseInt(body.slice(128, 192), 16);
|
|
107
|
+
return { data: ('0x' + body.slice(192, 192 + dataLen * 2)), salt };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 反事实地址预测:`factory.getAddress(data, salt)` view(纯 CREATE2,无合约创建)。
|
|
111
|
+
* 不依赖 `EntryPoint.getSenderAddress`(部分 RPC 拒绝 eth_call 内的 CREATE2 模拟)。
|
|
112
|
+
*/
|
|
113
|
+
async function predictAccountAddress(params) {
|
|
114
|
+
const { factory, factoryData } = encodeKernelFactoryData(params.chainConfig, params.owner, params.salt ?? 0n);
|
|
115
|
+
const { data, salt } = unpackFactoryData(factoryData);
|
|
116
|
+
return (await params.client.readContract({
|
|
117
|
+
address: factory,
|
|
118
|
+
abi: KernelFactoryGetAddressAbi,
|
|
119
|
+
functionName: 'getAddress',
|
|
120
|
+
args: [data, salt],
|
|
121
|
+
}));
|
|
122
|
+
}
|
package/dist/aa/types.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export interface ChainAAConfig {
|
|
|
15
15
|
rpcUrl?: string;
|
|
16
16
|
/** Kernel 版本(缺省 '0.3.1') */
|
|
17
17
|
kernelVersion?: string;
|
|
18
|
+
/** Kernel v3 factory(首笔部署用;缺省取内置三链默认) */
|
|
19
|
+
kernelFactory?: Address;
|
|
20
|
+
/** Kernel v3 root validator(ECDSA;缺省取内置三链默认) */
|
|
21
|
+
rootValidator?: Address;
|
|
18
22
|
/** Keel(v1) session validator 副本(slot i → 第 i 个;append-only) */
|
|
19
23
|
sessionValidatorCopies?: Address[];
|
|
20
24
|
/** Keel(v1) policy hook 副本(与 validator 成对) */
|
package/dist/aa/userop.d.ts
CHANGED
|
@@ -7,6 +7,19 @@ export declare function encodeExecute(target: Address, value: bigint, data: Hex)
|
|
|
7
7
|
* 挂 KeelPolicyHook 的 session 配置下,Kernel 在验证期强制 callData 以它开头。
|
|
8
8
|
*/
|
|
9
9
|
export declare const EXECUTE_USER_OP_SELECTOR: Hex;
|
|
10
|
+
/** 批量执行单条(ERC-7579 Execution = (target, value, data)) */
|
|
11
|
+
export interface Execution {
|
|
12
|
+
target: Address;
|
|
13
|
+
value?: bigint;
|
|
14
|
+
data: Hex;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* BATCH execMode:CALLTYPE_BATCH(0x01) | EXECTYPE_DEFAULT(0x00) → 高 2 字节 0x0100。
|
|
18
|
+
* 用于 owner 侧撤销(validator + hook 两段一次上链)。
|
|
19
|
+
*/
|
|
20
|
+
export declare const BATCH_EXEC_MODE: Hex;
|
|
21
|
+
/** 编码 Kernel v3 批量调用:execute(BATCH_EXEC_MODE, abi.encode(Execution[])) */
|
|
22
|
+
export declare function encodeExecuteBatch(executions: Execution[]): Hex;
|
|
10
23
|
/** 外层包装:executeUserOp.selector ‖ execute(...)(Route C / 挂真 hook 时必需) */
|
|
11
24
|
export declare function encodeExecuteUserOp(executeCallData: Hex): Hex;
|
|
12
25
|
export interface BuildUserOpWithCallDataParams {
|
package/dist/aa/userop.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
// userOpHash = viem getUserOperationHash(v0.7 EIP-712,含 chainId 防跨链重放)
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.EXECUTE_USER_OP_SELECTOR = void 0;
|
|
9
|
+
exports.BATCH_EXEC_MODE = exports.EXECUTE_USER_OP_SELECTOR = void 0;
|
|
10
10
|
exports.encodeExecute = encodeExecute;
|
|
11
|
+
exports.encodeExecuteBatch = encodeExecuteBatch;
|
|
11
12
|
exports.encodeExecuteUserOp = encodeExecuteUserOp;
|
|
12
13
|
exports.buildUserOpWithCallData = buildUserOpWithCallData;
|
|
13
14
|
exports.getUserOpHash = getUserOpHash;
|
|
@@ -42,6 +43,27 @@ function encodeExecute(target, value, data) {
|
|
|
42
43
|
* 挂 KeelPolicyHook 的 session 配置下,Kernel 在验证期强制 callData 以它开头。
|
|
43
44
|
*/
|
|
44
45
|
exports.EXECUTE_USER_OP_SELECTOR = '0x8dd7712f';
|
|
46
|
+
/**
|
|
47
|
+
* BATCH execMode:CALLTYPE_BATCH(0x01) | EXECTYPE_DEFAULT(0x00) → 高 2 字节 0x0100。
|
|
48
|
+
* 用于 owner 侧撤销(validator + hook 两段一次上链)。
|
|
49
|
+
*/
|
|
50
|
+
exports.BATCH_EXEC_MODE = '0x0100000000000000000000000000000000000000000000000000000000000000';
|
|
51
|
+
const ExecutionTuple = [
|
|
52
|
+
{ name: 'target', type: 'address' },
|
|
53
|
+
{ name: 'value', type: 'uint256' },
|
|
54
|
+
{ name: 'data', type: 'bytes' },
|
|
55
|
+
];
|
|
56
|
+
/** 编码 Kernel v3 批量调用:execute(BATCH_EXEC_MODE, abi.encode(Execution[])) */
|
|
57
|
+
function encodeExecuteBatch(executions) {
|
|
58
|
+
return (0, viem_1.encodeFunctionData)({
|
|
59
|
+
abi: KernelV3ExecuteAbi,
|
|
60
|
+
functionName: 'execute',
|
|
61
|
+
args: [
|
|
62
|
+
exports.BATCH_EXEC_MODE,
|
|
63
|
+
(0, viem_1.encodeAbiParameters)([{ name: 'executions', type: 'tuple[]', components: ExecutionTuple }], [executions.map((e) => ({ target: e.target, value: e.value ?? 0n, data: e.data }))]),
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
45
67
|
/** 外层包装:executeUserOp.selector ‖ execute(...)(Route C / 挂真 hook 时必需) */
|
|
46
68
|
function encodeExecuteUserOp(executeCallData) {
|
|
47
69
|
return (0, viem_1.concatHex)([exports.EXECUTE_USER_OP_SELECTOR, executeCallData]);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { X402Accept, X402PaymentRequired, X402ResourceInfo } from './x402';
|
|
2
|
+
/** CAIP-2 Solana network 前缀(如 `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`)。 */
|
|
3
|
+
export declare const SOLANA_NETWORK_PREFIX = "solana:";
|
|
4
|
+
/** Solana x402 签名域分隔标签。 */
|
|
5
|
+
export declare const SOLANA_X402_DOMAIN = "x402-solana-v2";
|
|
6
|
+
/** Solana 原生资产标签(与账本 `asset` 列一致,金额单位为 lamports)。 */
|
|
7
|
+
export declare const SOLANA_X402_ASSET = "sol";
|
|
8
|
+
export interface X402SolanaPaymentPayload {
|
|
9
|
+
x402Version: 2;
|
|
10
|
+
resource?: X402ResourceInfo;
|
|
11
|
+
accepted: X402Accept;
|
|
12
|
+
payload: {
|
|
13
|
+
method: string;
|
|
14
|
+
url: string;
|
|
15
|
+
/** 重放保护随机串(字符串即可;缺省由 txHash + 时间派生) */
|
|
16
|
+
salt: string;
|
|
17
|
+
/** 链上付款交易签名(base58) */
|
|
18
|
+
txHash: string;
|
|
19
|
+
/** 付款人公钥(base58) */
|
|
20
|
+
payer: string;
|
|
21
|
+
};
|
|
22
|
+
extensions?: Record<string, unknown>;
|
|
23
|
+
/** base58(ed25519 signature, 64B) */
|
|
24
|
+
signature: string;
|
|
25
|
+
}
|
|
26
|
+
/** 链上付款参数:向 `to` 转 `lamports`,原生 SOL。 */
|
|
27
|
+
export interface X402SolanaPayment {
|
|
28
|
+
to: string;
|
|
29
|
+
lamports: bigint;
|
|
30
|
+
asset: typeof SOLANA_X402_ASSET;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Solana 会话签名器:只需公钥 + 对原始消息签名。
|
|
34
|
+
* 私钥始终留在客户端(session key / 智能账户),SDK 不接触。
|
|
35
|
+
*/
|
|
36
|
+
export interface X402SolanaSigner {
|
|
37
|
+
/** base58 公钥(= payload.payer) */
|
|
38
|
+
publicKey: string;
|
|
39
|
+
/** 返回 64B 签名:Uint8Array 或 base58 字符串 */
|
|
40
|
+
signMessage(message: Uint8Array): Promise<Uint8Array | string>;
|
|
41
|
+
}
|
|
42
|
+
export interface PayX402WithSolanaParams {
|
|
43
|
+
challenge: X402PaymentRequired;
|
|
44
|
+
/** 不传则自动选第一个 Solana accept */
|
|
45
|
+
accept?: X402Accept;
|
|
46
|
+
/** 发起链上转账并返回交易签名(base58) */
|
|
47
|
+
sendPayment: (payment: X402SolanaPayment) => Promise<{
|
|
48
|
+
signature: string;
|
|
49
|
+
payer?: string;
|
|
50
|
+
}>;
|
|
51
|
+
signer: X402SolanaSigner;
|
|
52
|
+
/** 等交易确认(建议传入,避免服务端校验时还查不到) */
|
|
53
|
+
waitForConfirmation?: (signature: string) => Promise<unknown>;
|
|
54
|
+
method?: string;
|
|
55
|
+
url?: string;
|
|
56
|
+
salt?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface PayX402WithSolanaResult {
|
|
59
|
+
/** 可直接写入 `PAYMENT-SIGNATURE` 头 */
|
|
60
|
+
header: string;
|
|
61
|
+
payload: X402SolanaPaymentPayload;
|
|
62
|
+
/** 链上付款交易签名(= payload.payload.txHash) */
|
|
63
|
+
txHash: string;
|
|
64
|
+
payment: X402SolanaPayment;
|
|
65
|
+
accept: X402Accept;
|
|
66
|
+
}
|
|
67
|
+
/** base58 编码(仅用于把 64B ed25519 签名转成 wire 形态)。 */
|
|
68
|
+
export declare function encodeBase58(bytes: Uint8Array): string;
|
|
69
|
+
export declare function isSolanaAccept(accept: X402Accept): boolean;
|
|
70
|
+
/** 选 Solana accept:优先 exact;可按 network 精确过滤。 */
|
|
71
|
+
export declare function selectSolanaAccept(challenge: X402PaymentRequired, opts?: {
|
|
72
|
+
network?: string;
|
|
73
|
+
}): X402Accept;
|
|
74
|
+
/** 按 accept 组装转账参数(原生 SOL)。 */
|
|
75
|
+
export declare function buildSolanaX402Payment(accept: X402Accept): X402SolanaPayment;
|
|
76
|
+
/** 构造待签消息(必须与 payments `buildSolanaPaymentMessage` 逐字节一致)。 */
|
|
77
|
+
export declare function buildSolanaX402Message(payload: X402SolanaPaymentPayload): Uint8Array;
|
|
78
|
+
/** 由链上付款结果构造未签名的 payload。 */
|
|
79
|
+
export declare function buildSolanaX402Payload(args: {
|
|
80
|
+
challenge: X402PaymentRequired;
|
|
81
|
+
accepted: X402Accept;
|
|
82
|
+
txHash: string;
|
|
83
|
+
payer: string;
|
|
84
|
+
method?: string;
|
|
85
|
+
url?: string;
|
|
86
|
+
salt?: string;
|
|
87
|
+
}): X402SolanaPaymentPayload;
|
|
88
|
+
/**
|
|
89
|
+
* 用 Solana 会话密钥签 x402 payload,返回 base58 签名。
|
|
90
|
+
* 写入 `payload.signature` 后即可 base64 成 `PAYMENT-SIGNATURE`。
|
|
91
|
+
*/
|
|
92
|
+
export declare function signX402WithSolanaSession(payload: X402SolanaPaymentPayload, signer: X402SolanaSigner): Promise<string>;
|
|
93
|
+
/** 把已签名的 payload 编码成 `PAYMENT-SIGNATURE` 头。 */
|
|
94
|
+
export declare function encodeSolanaPaymentSignature(payload: X402SolanaPaymentPayload): string;
|
|
95
|
+
/**
|
|
96
|
+
* 一次完成「链上转账 → 等确认 → x402 凭证签名」。
|
|
97
|
+
* 转账由调用方注入(可用 aa-relay `/solana/submit` 广播,gas 可垫付)。
|
|
98
|
+
*/
|
|
99
|
+
export declare function payX402WithSolana(params: PayX402WithSolanaParams): Promise<PayX402WithSolanaResult>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// x402 v2 · Solana(verifyOnly)客户端
|
|
4
|
+
//
|
|
5
|
+
// 结算模型与 EVM 一致:客户端自己广播 SOL 转账,服务端只做「验签 + 链上校验 +
|
|
6
|
+
// 入账」,没有 facilitator `/settle`。
|
|
7
|
+
//
|
|
8
|
+
// 与 payments 服务端(`protocol/x402-v2.ts`)逐字节对齐:
|
|
9
|
+
// message = ["x402-solana-v2", scheme, network, amount, asset, payTo,
|
|
10
|
+
// method, url, salt, txHash].join("\n")
|
|
11
|
+
// header = base64(JSON({ x402Version, resource, accepted,
|
|
12
|
+
// payload:{method,url,salt,txHash,payer}, signature }))
|
|
13
|
+
//
|
|
14
|
+
// EVM 用 EIP-712 把「accepted + payload」绑定到付款人;SVM 没有等价的结构化
|
|
15
|
+
// 签名,改用 ed25519 对上面这条确定性消息签名,payer 由 `payload.payer` 显式给出
|
|
16
|
+
// (ed25519 无法从签名恢复公钥),服务端再用该公钥验签 + 校验链上同址。
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.SOLANA_X402_ASSET = exports.SOLANA_X402_DOMAIN = exports.SOLANA_NETWORK_PREFIX = void 0;
|
|
20
|
+
exports.encodeBase58 = encodeBase58;
|
|
21
|
+
exports.isSolanaAccept = isSolanaAccept;
|
|
22
|
+
exports.selectSolanaAccept = selectSolanaAccept;
|
|
23
|
+
exports.buildSolanaX402Payment = buildSolanaX402Payment;
|
|
24
|
+
exports.buildSolanaX402Message = buildSolanaX402Message;
|
|
25
|
+
exports.buildSolanaX402Payload = buildSolanaX402Payload;
|
|
26
|
+
exports.signX402WithSolanaSession = signX402WithSolanaSession;
|
|
27
|
+
exports.encodeSolanaPaymentSignature = encodeSolanaPaymentSignature;
|
|
28
|
+
exports.payX402WithSolana = payX402WithSolana;
|
|
29
|
+
const x402_1 = require("./x402");
|
|
30
|
+
/** CAIP-2 Solana network 前缀(如 `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`)。 */
|
|
31
|
+
exports.SOLANA_NETWORK_PREFIX = 'solana:';
|
|
32
|
+
/** Solana x402 签名域分隔标签。 */
|
|
33
|
+
exports.SOLANA_X402_DOMAIN = 'x402-solana-v2';
|
|
34
|
+
/** Solana 原生资产标签(与账本 `asset` 列一致,金额单位为 lamports)。 */
|
|
35
|
+
exports.SOLANA_X402_ASSET = 'sol';
|
|
36
|
+
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
37
|
+
/** base58 编码(仅用于把 64B ed25519 签名转成 wire 形态)。 */
|
|
38
|
+
function encodeBase58(bytes) {
|
|
39
|
+
let zeros = 0;
|
|
40
|
+
while (zeros < bytes.length && bytes[zeros] === 0)
|
|
41
|
+
zeros++;
|
|
42
|
+
const digits = [];
|
|
43
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
44
|
+
let carry = bytes[i];
|
|
45
|
+
for (let j = 0; j < digits.length; j++) {
|
|
46
|
+
carry += digits[j] << 8;
|
|
47
|
+
digits[j] = carry % 58;
|
|
48
|
+
carry = (carry / 58) | 0;
|
|
49
|
+
}
|
|
50
|
+
while (carry > 0) {
|
|
51
|
+
digits.push(carry % 58);
|
|
52
|
+
carry = (carry / 58) | 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let out = '1'.repeat(zeros);
|
|
56
|
+
for (let i = digits.length - 1; i >= 0; i--)
|
|
57
|
+
out += BASE58_ALPHABET[digits[i]];
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function isSolanaAccept(accept) {
|
|
61
|
+
return (String(accept.network ?? '').startsWith(exports.SOLANA_NETWORK_PREFIX) &&
|
|
62
|
+
String(accept.asset ?? '').toLowerCase() === exports.SOLANA_X402_ASSET);
|
|
63
|
+
}
|
|
64
|
+
/** 选 Solana accept:优先 exact;可按 network 精确过滤。 */
|
|
65
|
+
function selectSolanaAccept(challenge, opts = {}) {
|
|
66
|
+
const matches = challenge.accepts.filter((a) => isSolanaAccept(a) && (opts.network === undefined || a.network === opts.network));
|
|
67
|
+
const pick = matches.find((a) => a.scheme === 'exact') ?? matches[0];
|
|
68
|
+
if (!pick)
|
|
69
|
+
throw new Error('[keel-sdk] no Solana accept in this x402 challenge');
|
|
70
|
+
return pick;
|
|
71
|
+
}
|
|
72
|
+
/** 按 accept 组装转账参数(原生 SOL)。 */
|
|
73
|
+
function buildSolanaX402Payment(accept) {
|
|
74
|
+
if (!isSolanaAccept(accept)) {
|
|
75
|
+
throw new Error(`[keel-sdk] accept is not Solana native: ${accept.network}/${accept.asset}`);
|
|
76
|
+
}
|
|
77
|
+
const lamports = BigInt(accept.amount);
|
|
78
|
+
if (lamports <= 0n)
|
|
79
|
+
throw new Error('[keel-sdk] x402 amount must be > 0');
|
|
80
|
+
if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(accept.payTo)) {
|
|
81
|
+
throw new Error(`[keel-sdk] invalid Solana payTo: ${accept.payTo}`);
|
|
82
|
+
}
|
|
83
|
+
return { to: accept.payTo, lamports, asset: exports.SOLANA_X402_ASSET };
|
|
84
|
+
}
|
|
85
|
+
/** 构造待签消息(必须与 payments `buildSolanaPaymentMessage` 逐字节一致)。 */
|
|
86
|
+
function buildSolanaX402Message(payload) {
|
|
87
|
+
const { accepted } = payload;
|
|
88
|
+
return new TextEncoder().encode([
|
|
89
|
+
exports.SOLANA_X402_DOMAIN,
|
|
90
|
+
accepted.scheme,
|
|
91
|
+
accepted.network,
|
|
92
|
+
accepted.amount,
|
|
93
|
+
accepted.asset,
|
|
94
|
+
accepted.payTo,
|
|
95
|
+
payload.payload.method,
|
|
96
|
+
payload.payload.url,
|
|
97
|
+
payload.payload.salt,
|
|
98
|
+
payload.payload.txHash,
|
|
99
|
+
].join('\n'));
|
|
100
|
+
}
|
|
101
|
+
/** 由链上付款结果构造未签名的 payload。 */
|
|
102
|
+
function buildSolanaX402Payload(args) {
|
|
103
|
+
return {
|
|
104
|
+
x402Version: 2,
|
|
105
|
+
resource: args.challenge.resource,
|
|
106
|
+
accepted: args.accepted,
|
|
107
|
+
payload: {
|
|
108
|
+
method: args.method ?? 'GET',
|
|
109
|
+
url: args.url ?? args.challenge.resource?.url ?? '',
|
|
110
|
+
salt: args.salt ?? `${args.txHash}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
|
|
111
|
+
txHash: args.txHash,
|
|
112
|
+
payer: args.payer,
|
|
113
|
+
},
|
|
114
|
+
signature: '',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 用 Solana 会话密钥签 x402 payload,返回 base58 签名。
|
|
119
|
+
* 写入 `payload.signature` 后即可 base64 成 `PAYMENT-SIGNATURE`。
|
|
120
|
+
*/
|
|
121
|
+
async function signX402WithSolanaSession(payload, signer) {
|
|
122
|
+
if (payload.payload.payer !== signer.publicKey) {
|
|
123
|
+
throw new Error('[keel-sdk] payload.payer does not match the signing publicKey');
|
|
124
|
+
}
|
|
125
|
+
const raw = await signer.signMessage(buildSolanaX402Message(payload));
|
|
126
|
+
const sig = typeof raw === 'string' ? raw : encodeBase58(raw);
|
|
127
|
+
if (!/^[1-9A-HJ-NP-Za-km-z]{64,88}$/.test(sig)) {
|
|
128
|
+
throw new Error('[keel-sdk] signMessage must return a 64-byte ed25519 signature');
|
|
129
|
+
}
|
|
130
|
+
return sig;
|
|
131
|
+
}
|
|
132
|
+
/** 把已签名的 payload 编码成 `PAYMENT-SIGNATURE` 头。 */
|
|
133
|
+
function encodeSolanaPaymentSignature(payload) {
|
|
134
|
+
return (0, x402_1.toBase64)(JSON.stringify(payload));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 一次完成「链上转账 → 等确认 → x402 凭证签名」。
|
|
138
|
+
* 转账由调用方注入(可用 aa-relay `/solana/submit` 广播,gas 可垫付)。
|
|
139
|
+
*/
|
|
140
|
+
async function payX402WithSolana(params) {
|
|
141
|
+
const accept = params.accept ?? selectSolanaAccept(params.challenge);
|
|
142
|
+
const payment = buildSolanaX402Payment(accept);
|
|
143
|
+
const sent = await params.sendPayment(payment);
|
|
144
|
+
const txHash = String(sent?.signature ?? '').trim();
|
|
145
|
+
if (!/^[1-9A-HJ-NP-Za-km-z]{64,88}$/.test(txHash)) {
|
|
146
|
+
throw new Error(`[keel-sdk] sendPayment must return a base58 tx signature, got ${txHash}`);
|
|
147
|
+
}
|
|
148
|
+
const payer = String(sent.payer ?? params.signer.publicKey);
|
|
149
|
+
if (payer !== params.signer.publicKey) {
|
|
150
|
+
throw new Error('[keel-sdk] sendPayment payer differs from the x402 signing key');
|
|
151
|
+
}
|
|
152
|
+
if (params.waitForConfirmation)
|
|
153
|
+
await params.waitForConfirmation(txHash);
|
|
154
|
+
const unsigned = buildSolanaX402Payload({
|
|
155
|
+
challenge: params.challenge,
|
|
156
|
+
accepted: accept,
|
|
157
|
+
txHash,
|
|
158
|
+
payer,
|
|
159
|
+
method: params.method,
|
|
160
|
+
url: params.url,
|
|
161
|
+
salt: params.salt,
|
|
162
|
+
});
|
|
163
|
+
unsigned.signature = await signX402WithSolanaSession(unsigned, params.signer);
|
|
164
|
+
return {
|
|
165
|
+
header: encodeSolanaPaymentSignature(unsigned),
|
|
166
|
+
payload: unsigned,
|
|
167
|
+
txHash,
|
|
168
|
+
payment,
|
|
169
|
+
accept,
|
|
170
|
+
};
|
|
171
|
+
}
|
package/dist/http.d.ts
CHANGED
package/dist/http.js
CHANGED
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.HttpClient = exports.SDK_VERSION = void 0;
|
|
7
7
|
const errors_1 = require("./errors");
|
|
8
8
|
/** SDK 版本(契约 §5,随包版本同步)。 */
|
|
9
|
-
exports.SDK_VERSION = '2.
|
|
9
|
+
exports.SDK_VERSION = '2.4.0';
|
|
10
10
|
/**
|
|
11
11
|
* 钱包签名的复用时长(ms):首次签名后在该窗口内复用,避免每个请求都触发钱包弹窗。
|
|
12
12
|
* 服务端会话 TTL 为 24h,此处取 1h 留足余量。
|
|
@@ -88,7 +88,7 @@ class HttpClient {
|
|
|
88
88
|
return { ...this.headers, ...this.walletToken.headers };
|
|
89
89
|
}
|
|
90
90
|
const timestamp = String(now);
|
|
91
|
-
const signature = await this.wallet.sign(`
|
|
91
|
+
const signature = await this.wallet.sign(`Keel auth: ${timestamp}`);
|
|
92
92
|
const headers = {
|
|
93
93
|
'x-wallet-address': this.wallet.address.toLowerCase(),
|
|
94
94
|
'x-wallet-timestamp': timestamp,
|
package/dist/wallet/index.d.ts
CHANGED
package/dist/wallet/index.js
CHANGED
|
@@ -20,6 +20,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
20
|
__exportStar(require("./types"), exports);
|
|
21
21
|
__exportStar(require("./solana-account-types"), exports);
|
|
22
22
|
__exportStar(require("./solana-execution-types"), exports);
|
|
23
|
+
__exportStar(require("./solana-dex-types"), exports);
|
|
23
24
|
__exportStar(require("./mpc-types"), exports);
|
|
24
25
|
__exportStar(require("./wallet-api"), exports);
|
|
25
26
|
__exportStar(require("../aa"), exports);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { SolanaSubmitResult } from './solana-execution-types';
|
|
2
|
+
/** DEX 报价(只读,不构建交易) */
|
|
3
|
+
export interface SolanaDexQuoteParams {
|
|
4
|
+
chain: string;
|
|
5
|
+
inputMint: string;
|
|
6
|
+
outputMint: string;
|
|
7
|
+
/** base units(十进制整数) */
|
|
8
|
+
amount: string | number;
|
|
9
|
+
slippageBps?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SolanaDexQuoteResult {
|
|
12
|
+
chain: string;
|
|
13
|
+
/** Jupiter 原始报价(字段随聚合器版本变化,按需读取) */
|
|
14
|
+
quote: Record<string, unknown>;
|
|
15
|
+
inputMint: string;
|
|
16
|
+
inAmount: string;
|
|
17
|
+
outputMint: string;
|
|
18
|
+
outAmount: string;
|
|
19
|
+
otherAmountThreshold: string;
|
|
20
|
+
priceImpactPct: string;
|
|
21
|
+
slippageBps: number;
|
|
22
|
+
}
|
|
23
|
+
/** `ProgramInteraction` policy 安装入参(`/dex/policy` 与 `/dex/install` 通用) */
|
|
24
|
+
export interface SolanaDexPolicyParams {
|
|
25
|
+
chain: string;
|
|
26
|
+
blockhash: string;
|
|
27
|
+
feePayer: string;
|
|
28
|
+
creator: string;
|
|
29
|
+
settingsPda: string;
|
|
30
|
+
transactionIndex: string | number;
|
|
31
|
+
/** policy PDA 的 u64 seed(= 调用前链上 `settings.policySeed + 1`) */
|
|
32
|
+
seed: string | number;
|
|
33
|
+
/** 作为 policy 唯一签名者的 session key 公钥 */
|
|
34
|
+
sessionKey: string;
|
|
35
|
+
rentPayer?: string;
|
|
36
|
+
accountIndex?: number;
|
|
37
|
+
/** 缺省 `[1,2,4]`(Initiate | Vote | Execute) */
|
|
38
|
+
permissions?: number[];
|
|
39
|
+
spendingLimits?: Array<{
|
|
40
|
+
mint: string;
|
|
41
|
+
/** base units(十进制整数) */
|
|
42
|
+
maxPerPeriod: string | number;
|
|
43
|
+
period?: number;
|
|
44
|
+
start?: string | number;
|
|
45
|
+
expiration?: string | number;
|
|
46
|
+
}>;
|
|
47
|
+
/** 缺省 = relay 配置里的 program 白名单(Jupiter + 直连 DEX + 辅助 program) */
|
|
48
|
+
constraints?: Array<{
|
|
49
|
+
programId: string;
|
|
50
|
+
accountConstraints?: Array<{
|
|
51
|
+
accountIndex: number;
|
|
52
|
+
pubkeys: string[];
|
|
53
|
+
owner?: string;
|
|
54
|
+
}>;
|
|
55
|
+
dataConstraints?: Array<{
|
|
56
|
+
offset: string | number;
|
|
57
|
+
value?: string;
|
|
58
|
+
operator?: number;
|
|
59
|
+
}>;
|
|
60
|
+
}>;
|
|
61
|
+
memo?: string;
|
|
62
|
+
}
|
|
63
|
+
/** 安装计划中的一步(未签名交易,按 `steps` 顺序签名广播) */
|
|
64
|
+
export interface SolanaDexInstallStep {
|
|
65
|
+
step: 'createSettingsTransaction' | 'createProposal' | 'approveProposal' | 'executeSettingsTransaction';
|
|
66
|
+
/** 需要本地签名的公钥(base58) */
|
|
67
|
+
signers: string[];
|
|
68
|
+
instructionCount: number;
|
|
69
|
+
/** base64(v0) */
|
|
70
|
+
transaction: string;
|
|
71
|
+
}
|
|
72
|
+
export interface SolanaDexInstallResult {
|
|
73
|
+
chain: string;
|
|
74
|
+
policyPda: string;
|
|
75
|
+
/** 四步共用同一个 blockhash;过期未走完需重新调用 */
|
|
76
|
+
blockhash: string;
|
|
77
|
+
steps: SolanaDexInstallStep[];
|
|
78
|
+
}
|
|
79
|
+
export interface SolanaDexSwapParams {
|
|
80
|
+
chain: string;
|
|
81
|
+
blockhash: string;
|
|
82
|
+
feePayer: string;
|
|
83
|
+
/** policy PDA */
|
|
84
|
+
policy: string;
|
|
85
|
+
/** smart account(vault)PDA */
|
|
86
|
+
vaultPda: string;
|
|
87
|
+
sessionKey: string;
|
|
88
|
+
inputMint: string;
|
|
89
|
+
outputMint: string;
|
|
90
|
+
amount: string | number;
|
|
91
|
+
accountIndex?: number;
|
|
92
|
+
numSigners?: number;
|
|
93
|
+
slippageBps?: number;
|
|
94
|
+
wrapAndUnwrapSol?: boolean;
|
|
95
|
+
dynamicComputeUnitLimit?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* policy 创建时 `instructionsConstraints` 的顺序(programId 数组)。
|
|
98
|
+
* 缺省取 relay 配置白名单顺序;链上要求逐条指令都能映射到一条约束。
|
|
99
|
+
*/
|
|
100
|
+
policyPrograms?: string[];
|
|
101
|
+
}
|
|
102
|
+
export interface SolanaDexSwapResult {
|
|
103
|
+
chain: string;
|
|
104
|
+
inAmount: string;
|
|
105
|
+
outAmount: string;
|
|
106
|
+
otherAmountThreshold: string;
|
|
107
|
+
priceImpactPct: string;
|
|
108
|
+
slippageBps: number;
|
|
109
|
+
innerInstructionCount: number;
|
|
110
|
+
instructionConstraintIndices: number[];
|
|
111
|
+
addressLookupTables: number;
|
|
112
|
+
transactionBytes: number;
|
|
113
|
+
/** 未签名交易(base64 v0);签名后经 `submit` 广播才会计费 */
|
|
114
|
+
transaction: string;
|
|
115
|
+
instructionCount?: number;
|
|
116
|
+
/** 计费提示:签名后发到该端点,relay 按 fee payer 收固定服务费 */
|
|
117
|
+
billing?: {
|
|
118
|
+
endpoint: string;
|
|
119
|
+
feePayer: string;
|
|
120
|
+
feeLamports: string;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 本地签名回调:入参为未签名交易(base64 v0)与需要签名的公钥,
|
|
125
|
+
* 返回已签名的 base64 交易。SDK 不内置 Solana 依赖,由调用方用
|
|
126
|
+
* `@solana/web3.js`(`VersionedTransaction.deserialize` → `sign`)实现。
|
|
127
|
+
*/
|
|
128
|
+
export type SolanaTransactionSigner = (transaction: string, signers: string[]) => Promise<string> | string;
|
|
129
|
+
/** `dexSwapAndSubmit`:一次完成「取未签名交易 → 本地签名 → 经 relay 计费广播」。 */
|
|
130
|
+
export interface SolanaDexSwapAndSubmitParams extends SolanaDexSwapParams {
|
|
131
|
+
sign: SolanaTransactionSigner;
|
|
132
|
+
}
|
|
133
|
+
export interface SolanaDexSwapAndSubmitResult extends SolanaDexSwapResult {
|
|
134
|
+
/** relay `/submit` 的广播回执(计费发生在这里) */
|
|
135
|
+
submit: SolanaSubmitResult;
|
|
136
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { KeelEnvelope } from '../types';
|
|
|
3
3
|
import type { CreateWalletParams, WalletCreated, ImportWalletParams, SignParams, SignResult, SendParams, SendResult, ChainParams, ChainResult, WalletBalanceParams, WalletBalanceResult, SessionCreateParams, SessionCreated, PaymasterInfo, PaymasterVoucherParams, PaymasterVoucher } from './types';
|
|
4
4
|
import type { SolanaDeriveAccountParams, SolanaDerivedAccount, SolanaCreateAccountParams, SolanaInstallPolicyParams, SolanaCreatePolicyTransactionParams, SolanaCreateProposalParams, SolanaApproveProposalParams, SolanaExecutePolicyTransactionParams, SolanaRemovePolicyParams } from './solana-account-types';
|
|
5
5
|
import type { SolanaExecutePolicyPayloadParams, SolanaCloseSettingsParams, SolanaCloseTransactionParams, SolanaExecutePolicyParams, SolanaExecuteParams, SolanaRemoveSignerParams, SolanaSubmitParams, SolanaUnsignedTransaction, SolanaSubmitResult } from './solana-execution-types';
|
|
6
|
+
import type { SolanaDexQuoteParams, SolanaDexQuoteResult, SolanaDexPolicyParams, SolanaDexInstallResult, SolanaDexSwapParams, SolanaDexSwapResult, SolanaDexSwapAndSubmitParams, SolanaDexSwapAndSubmitResult } from './solana-dex-types';
|
|
6
7
|
import type { MpcSendCodeParams, MpcVerifyParams, MpcBalanceParams, MpcSignMessageParams, MpcSignTypedDataParams, MpcSendTransactionParams, MpcContractCallParams, MpcContractWriteParams, MpcGasEstimateParams, MpcRegisterParams, MpcRegisterResult, MpcSolanaSignParams, MpcSolanaSignResult, MpcSolanaSendParams, MpcSolanaSendResult } from './mpc-types';
|
|
7
8
|
export declare class WalletAPI {
|
|
8
9
|
private http;
|
|
@@ -171,6 +172,18 @@ export declare class WalletAPI {
|
|
|
171
172
|
execute: (p: SolanaExecuteParams) => Promise<SolanaUnsignedTransaction>;
|
|
172
173
|
removeSigner: (p: SolanaRemoveSignerParams) => Promise<SolanaUnsignedTransaction>;
|
|
173
174
|
submit: (p: SolanaSubmitParams) => Promise<SolanaSubmitResult>;
|
|
175
|
+
/** DEX:Jupiter 报价(只读,不构建交易)。 */
|
|
176
|
+
dexQuote: (p: SolanaDexQuoteParams) => Promise<SolanaDexQuoteResult>;
|
|
177
|
+
/** DEX:一站式 policy 安装——返回四笔未签名交易,按 `steps` 顺序本地签名广播。 */
|
|
178
|
+
dexInstallPolicy: (p: SolanaDexPolicyParams) => Promise<SolanaDexInstallResult>;
|
|
179
|
+
/** DEX:组装 ProgramInteraction swap 交易(未签名;签名后经 `submit` 广播并计费)。 */
|
|
180
|
+
dexSwap: (p: SolanaDexSwapParams) => Promise<SolanaDexSwapResult>;
|
|
181
|
+
/**
|
|
182
|
+
* DEX:一步完成 swap —— 取未签名交易 → `sign` 回调本地签名 → 经 `/submit` 广播。
|
|
183
|
+
* `sign` 由调用方提供(SDK 不内置 Solana 依赖),例如:
|
|
184
|
+
* `(b64) => { const tx = VersionedTransaction.deserialize(Buffer.from(b64, 'base64')); tx.sign([kp]); return Buffer.from(tx.serialize()).toString('base64') }`
|
|
185
|
+
*/
|
|
186
|
+
dexSwapAndSubmit: (p: SolanaDexSwapAndSubmitParams) => Promise<SolanaDexSwapAndSubmitResult>;
|
|
174
187
|
};
|
|
175
188
|
};
|
|
176
189
|
/** MPC(email 分片钱包)子域:canonical `/v1/wallet/mpc/*`。 */
|
|
@@ -153,6 +153,24 @@ class WalletAPI {
|
|
|
153
153
|
execute: (p) => this.http.post('/v1/wallet/session/solana/session/execute', p),
|
|
154
154
|
removeSigner: (p) => this.http.post('/v1/wallet/session/solana/session/remove-signer', p),
|
|
155
155
|
submit: (p) => this.http.post('/v1/wallet/session/solana/submit', p),
|
|
156
|
+
/** DEX:Jupiter 报价(只读,不构建交易)。 */
|
|
157
|
+
dexQuote: (p) => this.http.post('/v1/wallet/session/solana/dex/quote', p),
|
|
158
|
+
/** DEX:一站式 policy 安装——返回四笔未签名交易,按 `steps` 顺序本地签名广播。 */
|
|
159
|
+
dexInstallPolicy: (p) => this.http.post('/v1/wallet/session/solana/dex/install', p),
|
|
160
|
+
/** DEX:组装 ProgramInteraction swap 交易(未签名;签名后经 `submit` 广播并计费)。 */
|
|
161
|
+
dexSwap: (p) => this.http.post('/v1/wallet/session/solana/dex/swap', p),
|
|
162
|
+
/**
|
|
163
|
+
* DEX:一步完成 swap —— 取未签名交易 → `sign` 回调本地签名 → 经 `/submit` 广播。
|
|
164
|
+
* `sign` 由调用方提供(SDK 不内置 Solana 依赖),例如:
|
|
165
|
+
* `(b64) => { const tx = VersionedTransaction.deserialize(Buffer.from(b64, 'base64')); tx.sign([kp]); return Buffer.from(tx.serialize()).toString('base64') }`
|
|
166
|
+
*/
|
|
167
|
+
dexSwapAndSubmit: async (p) => {
|
|
168
|
+
const { sign, ...swap } = p;
|
|
169
|
+
const built = await this.http.post('/v1/wallet/session/solana/dex/swap', swap);
|
|
170
|
+
const signed = await sign(built.transaction, [...new Set([swap.feePayer, swap.sessionKey])]);
|
|
171
|
+
const submit = await this.http.post('/v1/wallet/session/solana/submit', { chain: swap.chain, transaction: signed });
|
|
172
|
+
return { ...built, submit };
|
|
173
|
+
},
|
|
156
174
|
},
|
|
157
175
|
};
|
|
158
176
|
/** MPC(email 分片钱包)子域:canonical `/v1/wallet/mpc/*`。 */
|
package/package.json
CHANGED