@keelcodes/sdk 2.4.0 → 2.5.1
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/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/wallet/index.d.ts +1 -0
- package/dist/wallet/index.js +1 -0
- package/dist/wallet/solana-dex-provider.d.ts +56 -0
- package/dist/wallet/solana-dex-provider.js +94 -0
- package/dist/wallet/solana-dex-types.d.ts +33 -0
- package/dist/wallet/solana-execution-types.d.ts +14 -0
- package/dist/wallet/wallet-api.js +52 -4
- package/package.json +1 -1
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.5.1';
|
|
10
10
|
/**
|
|
11
11
|
* 钱包签名的复用时长(ms):首次签名后在该窗口内复用,避免每个请求都触发钱包弹窗。
|
|
12
12
|
* 服务端会话 TTL 为 24h,此处取 1h 留足余量。
|
package/dist/wallet/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './types';
|
|
|
2
2
|
export * from './solana-account-types';
|
|
3
3
|
export * from './solana-execution-types';
|
|
4
4
|
export * from './solana-dex-types';
|
|
5
|
+
export * from './solana-dex-provider';
|
|
5
6
|
export * from './mpc-types';
|
|
6
7
|
export * from './wallet-api';
|
|
7
8
|
export * from '../aa';
|
package/dist/wallet/index.js
CHANGED
|
@@ -21,6 +21,7 @@ __exportStar(require("./types"), exports);
|
|
|
21
21
|
__exportStar(require("./solana-account-types"), exports);
|
|
22
22
|
__exportStar(require("./solana-execution-types"), exports);
|
|
23
23
|
__exportStar(require("./solana-dex-types"), exports);
|
|
24
|
+
__exportStar(require("./solana-dex-provider"), exports);
|
|
24
25
|
__exportStar(require("./mpc-types"), exports);
|
|
25
26
|
__exportStar(require("./wallet-api"), exports);
|
|
26
27
|
__exportStar(require("../aa"), exports);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { SolanaDexQuoteParams } from './solana-dex-types';
|
|
2
|
+
export interface DexInstructionAccount {
|
|
3
|
+
pubkey: string;
|
|
4
|
+
isSigner?: boolean;
|
|
5
|
+
isWritable?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** 归一化后的单条内层指令(data 为 base64) */
|
|
8
|
+
export interface DexInstruction {
|
|
9
|
+
programId: string;
|
|
10
|
+
accounts: DexInstructionAccount[];
|
|
11
|
+
data: string;
|
|
12
|
+
}
|
|
13
|
+
/** 统一报价(字段口径与 relay `/dex/swap` 的响应一致) */
|
|
14
|
+
export interface DexProviderQuote {
|
|
15
|
+
inAmount: string;
|
|
16
|
+
outAmount: string;
|
|
17
|
+
otherAmountThreshold: string;
|
|
18
|
+
priceImpactPct: string;
|
|
19
|
+
slippageBps: number;
|
|
20
|
+
/** provider 原始响应,供 `swapInstructions` 复用(不会发给 relay) */
|
|
21
|
+
raw?: unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface DexProviderSwapRequest {
|
|
24
|
+
inputMint: string;
|
|
25
|
+
outputMint: string;
|
|
26
|
+
amount: string | number;
|
|
27
|
+
slippageBps?: number;
|
|
28
|
+
/** swap 的 user(= Squads vault PDA) */
|
|
29
|
+
userPublicKey: string;
|
|
30
|
+
/** `quote()` 返回的 `raw` */
|
|
31
|
+
quote?: unknown;
|
|
32
|
+
}
|
|
33
|
+
export interface DexProviderSwap {
|
|
34
|
+
instructions: DexInstruction[];
|
|
35
|
+
addressLookupTableAddresses?: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface DexProvider {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
quote(req: SolanaDexQuoteParams): Promise<DexProviderQuote>;
|
|
40
|
+
swapInstructions(req: DexProviderSwapRequest): Promise<DexProviderSwap>;
|
|
41
|
+
}
|
|
42
|
+
export interface JupiterProviderOptions {
|
|
43
|
+
/** 默认 https://lite-api.jup.ag */
|
|
44
|
+
baseUrl?: string;
|
|
45
|
+
/** Jupiter Pro(或自建网关)的 key,非空时作为 `x-api-key` 头 */
|
|
46
|
+
apiKey?: string;
|
|
47
|
+
/** 便于测试注入;默认全局 fetch */
|
|
48
|
+
fetchImpl?: typeof fetch;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Jupiter(含任意 Jupiter 兼容端点)provider。
|
|
52
|
+
* 只需要 base URL 与可选 key,B 端即可用自己的额度/网关取价取指令。
|
|
53
|
+
*/
|
|
54
|
+
export declare function jupiterProvider(opts?: JupiterProviderOptions): DexProvider;
|
|
55
|
+
/** B 端自定义 provider 的便捷校验:确保 relay 需要的字段齐全。 */
|
|
56
|
+
export declare function assertDexProvider(provider: DexProvider): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jupiterProvider = jupiterProvider;
|
|
4
|
+
exports.assertDexProvider = assertDexProvider;
|
|
5
|
+
/** Jupiter `/swap/v1/swap-instructions` → 有顺序的指令列表(与 relay 内部口径一致)。 */
|
|
6
|
+
function collectJupiterInstructions(body) {
|
|
7
|
+
const out = [];
|
|
8
|
+
const push = (ix) => {
|
|
9
|
+
if (ix && ix.programId)
|
|
10
|
+
out.push({ programId: ix.programId, accounts: ix.accounts ?? [], data: ix.data });
|
|
11
|
+
};
|
|
12
|
+
push(body?.tokenLedgerInstruction);
|
|
13
|
+
for (const ix of body?.computeBudgetInstructions ?? [])
|
|
14
|
+
push(ix);
|
|
15
|
+
for (const ix of body?.setupInstructions ?? [])
|
|
16
|
+
push(ix);
|
|
17
|
+
push(body?.swapInstruction);
|
|
18
|
+
push(body?.cleanupInstruction);
|
|
19
|
+
for (const ix of body?.otherInstructions ?? [])
|
|
20
|
+
push(ix);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Jupiter(含任意 Jupiter 兼容端点)provider。
|
|
25
|
+
* 只需要 base URL 与可选 key,B 端即可用自己的额度/网关取价取指令。
|
|
26
|
+
*/
|
|
27
|
+
function jupiterProvider(opts = {}) {
|
|
28
|
+
const baseUrl = (opts.baseUrl ?? 'https://lite-api.jup.ag').replace(/\/+$/, '');
|
|
29
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
30
|
+
const headers = () => ({
|
|
31
|
+
'content-type': 'application/json',
|
|
32
|
+
...(opts.apiKey ? { 'x-api-key': opts.apiKey } : {}),
|
|
33
|
+
});
|
|
34
|
+
async function readJson(res, path) {
|
|
35
|
+
const text = await res.text();
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
throw new Error(`jupiter ${path} failed: HTTP ${res.status} ${text.slice(0, 200)}`);
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(text);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new Error(`jupiter ${path} returned invalid JSON`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
name: 'jupiter',
|
|
47
|
+
async quote(req) {
|
|
48
|
+
const qs = new URLSearchParams({
|
|
49
|
+
inputMint: req.inputMint,
|
|
50
|
+
outputMint: req.outputMint,
|
|
51
|
+
amount: String(req.amount),
|
|
52
|
+
...(req.slippageBps === undefined ? {} : { slippageBps: String(req.slippageBps) }),
|
|
53
|
+
});
|
|
54
|
+
const path = `/swap/v1/quote?${qs.toString()}`;
|
|
55
|
+
const raw = await readJson(await doFetch(`${baseUrl}${path}`, { headers: headers() }), path);
|
|
56
|
+
return {
|
|
57
|
+
inAmount: String(raw.inAmount),
|
|
58
|
+
outAmount: String(raw.outAmount),
|
|
59
|
+
otherAmountThreshold: String(raw.otherAmountThreshold ?? ''),
|
|
60
|
+
priceImpactPct: String(raw.priceImpactPct ?? '0'),
|
|
61
|
+
slippageBps: Number(raw.slippageBps ?? req.slippageBps ?? 50),
|
|
62
|
+
raw,
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
async swapInstructions(req) {
|
|
66
|
+
const path = '/swap/v1/swap-instructions';
|
|
67
|
+
const body = {
|
|
68
|
+
quoteResponse: req.quote,
|
|
69
|
+
userPublicKey: req.userPublicKey,
|
|
70
|
+
wrapAndUnwrapSol: true,
|
|
71
|
+
dynamicComputeUnitLimit: true,
|
|
72
|
+
};
|
|
73
|
+
const raw = await readJson(await doFetch(`${baseUrl}${path}`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: headers(),
|
|
76
|
+
body: JSON.stringify(body),
|
|
77
|
+
}), path);
|
|
78
|
+
if (raw?.error)
|
|
79
|
+
throw new Error(`jupiter swap-instructions error: ${String(raw.error)}`);
|
|
80
|
+
return {
|
|
81
|
+
instructions: collectJupiterInstructions(raw),
|
|
82
|
+
addressLookupTableAddresses: Array.isArray(raw?.addressLookupTableAddresses)
|
|
83
|
+
? raw.addressLookupTableAddresses
|
|
84
|
+
: [],
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** B 端自定义 provider 的便捷校验:确保 relay 需要的字段齐全。 */
|
|
90
|
+
function assertDexProvider(provider) {
|
|
91
|
+
if (!provider || typeof provider.quote !== 'function' || typeof provider.swapInstructions !== 'function') {
|
|
92
|
+
throw new Error('dexProvider must implement quote() and swapInstructions()');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SolanaSubmitResult } from './solana-execution-types';
|
|
2
|
+
import type { DexInstruction, DexProvider } from './solana-dex-provider';
|
|
2
3
|
/** DEX 报价(只读,不构建交易) */
|
|
3
4
|
export interface SolanaDexQuoteParams {
|
|
4
5
|
chain: string;
|
|
@@ -96,8 +97,26 @@ export interface SolanaDexSwapParams {
|
|
|
96
97
|
/**
|
|
97
98
|
* policy 创建时 `instructionsConstraints` 的顺序(programId 数组)。
|
|
98
99
|
* 缺省取 relay 配置白名单顺序;链上要求逐条指令都能映射到一条约束。
|
|
100
|
+
* 使用 `dexProvider`(external 模式)时**必须**给出,relay 以它作为 program 白名单。
|
|
99
101
|
*/
|
|
100
102
|
policyPrograms?: string[];
|
|
103
|
+
/**
|
|
104
|
+
* 自定义 DEX/聚合器 provider(2026-10-24)。给出时 SDK 先本地取价取指令,
|
|
105
|
+
* 再以 `provider: 'external'` 交给 relay 组装交易;URL/key 不经过 relay。
|
|
106
|
+
*/
|
|
107
|
+
dexProvider?: DexProvider;
|
|
108
|
+
/** external 模式的报价回显(不参与风控/计费);`dexProvider` 会自动填充 */
|
|
109
|
+
quote?: {
|
|
110
|
+
inAmount?: string;
|
|
111
|
+
outAmount?: string;
|
|
112
|
+
otherAmountThreshold?: string;
|
|
113
|
+
priceImpactPct?: string;
|
|
114
|
+
slippageBps?: number;
|
|
115
|
+
};
|
|
116
|
+
/** external 模式的归一化内层指令(`dexProvider` 会自动填充) */
|
|
117
|
+
swapInstructions?: DexInstruction[];
|
|
118
|
+
/** external 模式的地址查找表(`dexProvider` 会自动填充) */
|
|
119
|
+
addressLookupTableAddresses?: string[];
|
|
101
120
|
}
|
|
102
121
|
export interface SolanaDexSwapResult {
|
|
103
122
|
chain: string;
|
|
@@ -119,6 +138,15 @@ export interface SolanaDexSwapResult {
|
|
|
119
138
|
feePayer: string;
|
|
120
139
|
feeLamports: string;
|
|
121
140
|
};
|
|
141
|
+
/** 同交易扣费信息(费用已在内层指令里;无需账本预付) */
|
|
142
|
+
fee?: {
|
|
143
|
+
mint: string;
|
|
144
|
+
symbol: string;
|
|
145
|
+
amountAtomic: string;
|
|
146
|
+
position: 'first' | 'last';
|
|
147
|
+
};
|
|
148
|
+
/** 费用凭证(HMAC 绑定本笔交易);`dexSwapAndSubmit` 会自动转交 `/submit` */
|
|
149
|
+
feeProof?: string;
|
|
122
150
|
}
|
|
123
151
|
/**
|
|
124
152
|
* 本地签名回调:入参为未签名交易(base64 v0)与需要签名的公钥,
|
|
@@ -129,6 +157,11 @@ export type SolanaTransactionSigner = (transaction: string, signers: string[]) =
|
|
|
129
157
|
/** `dexSwapAndSubmit`:一次完成「取未签名交易 → 本地签名 → 经 relay 计费广播」。 */
|
|
130
158
|
export interface SolanaDexSwapAndSubmitParams extends SolanaDexSwapParams {
|
|
131
159
|
sign: SolanaTransactionSigner;
|
|
160
|
+
/**
|
|
161
|
+
* 垫付模式下的扣费主体(客户地址)。fee payer 是平台热钱包时必须给,
|
|
162
|
+
* relay 会把「固定服务费 + 实际垫付 lamports」扣在该地址账本上。
|
|
163
|
+
*/
|
|
164
|
+
payer?: string;
|
|
132
165
|
}
|
|
133
166
|
export interface SolanaDexSwapAndSubmitResult extends SolanaDexSwapResult {
|
|
134
167
|
/** relay `/submit` 的广播回执(计费发生在这里) */
|
|
@@ -75,6 +75,16 @@ export interface SolanaRemoveSignerParams {
|
|
|
75
75
|
export interface SolanaSubmitParams {
|
|
76
76
|
chain: string;
|
|
77
77
|
transaction: string;
|
|
78
|
+
/**
|
|
79
|
+
* 垫付模式的扣费主体(客户 base58 地址)。仅当交易 fee payer 是平台热钱包时需要;
|
|
80
|
+
* relay 会把固定服务费 + 实际垫付 lamports 扣在该地址。
|
|
81
|
+
*/
|
|
82
|
+
payer?: string;
|
|
83
|
+
/**
|
|
84
|
+
* 同交易扣费凭证(`/dex/swap` 响应里的 `feeProof`)。给出且校验通过时,
|
|
85
|
+
* relay 不再从账本扣服务费(费用已在交易内由智能账户直接支付)。
|
|
86
|
+
*/
|
|
87
|
+
feeProof?: string;
|
|
78
88
|
}
|
|
79
89
|
/** 构建端点返回的未签名交易(base64)。 */
|
|
80
90
|
export interface SolanaUnsignedTransaction {
|
|
@@ -87,4 +97,8 @@ export interface SolanaSubmitResult {
|
|
|
87
97
|
chain: string;
|
|
88
98
|
signature: string;
|
|
89
99
|
feePayer?: string;
|
|
100
|
+
/** 同交易扣费:服务费已由智能账户在交易内支付(无账本扣费) */
|
|
101
|
+
fee?: {
|
|
102
|
+
mode: 'onchain';
|
|
103
|
+
};
|
|
90
104
|
}
|
|
@@ -158,17 +158,23 @@ class WalletAPI {
|
|
|
158
158
|
/** DEX:一站式 policy 安装——返回四笔未签名交易,按 `steps` 顺序本地签名广播。 */
|
|
159
159
|
dexInstallPolicy: (p) => this.http.post('/v1/wallet/session/solana/dex/install', p),
|
|
160
160
|
/** DEX:组装 ProgramInteraction swap 交易(未签名;签名后经 `submit` 广播并计费)。 */
|
|
161
|
-
dexSwap: (p) => this.http
|
|
161
|
+
dexSwap: (p) => buildDexSwapRequest(this.http, p),
|
|
162
162
|
/**
|
|
163
163
|
* DEX:一步完成 swap —— 取未签名交易 → `sign` 回调本地签名 → 经 `/submit` 广播。
|
|
164
164
|
* `sign` 由调用方提供(SDK 不内置 Solana 依赖),例如:
|
|
165
165
|
* `(b64) => { const tx = VersionedTransaction.deserialize(Buffer.from(b64, 'base64')); tx.sign([kp]); return Buffer.from(tx.serialize()).toString('base64') }`
|
|
166
166
|
*/
|
|
167
167
|
dexSwapAndSubmit: async (p) => {
|
|
168
|
-
const { sign, ...swap } = p;
|
|
169
|
-
const built = await this.http
|
|
168
|
+
const { sign, payer, ...swap } = p;
|
|
169
|
+
const built = await buildDexSwapRequest(this.http, swap);
|
|
170
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', {
|
|
171
|
+
const submit = await this.http.post('/v1/wallet/session/solana/submit', {
|
|
172
|
+
chain: swap.chain,
|
|
173
|
+
transaction: signed,
|
|
174
|
+
...(payer ? { payer } : {}),
|
|
175
|
+
// 同交易扣费:把构建时签发的凭证一并交给 /submit,避免账本重复扣费
|
|
176
|
+
...(built.feeProof ? { feeProof: built.feeProof } : {}),
|
|
177
|
+
});
|
|
172
178
|
return { ...built, submit };
|
|
173
179
|
},
|
|
174
180
|
},
|
|
@@ -202,3 +208,45 @@ class WalletAPI {
|
|
|
202
208
|
};
|
|
203
209
|
}
|
|
204
210
|
exports.WalletAPI = WalletAPI;
|
|
211
|
+
/**
|
|
212
|
+
* `/dex/swap` 的请求构造(2026-10-24):
|
|
213
|
+
* - 给了 `dexProvider`:本地取价取指令 → 以 `provider: 'external'` 交给 relay;
|
|
214
|
+
* - 只给了 `swapInstructions`(B 端自己取):直接透传为 external;
|
|
215
|
+
* - 都没给:原样发给 relay,由 relay 调内置 Jupiter(默认行为)。
|
|
216
|
+
*/
|
|
217
|
+
async function buildDexSwapRequest(http, p) {
|
|
218
|
+
const { dexProvider, quote, swapInstructions, addressLookupTableAddresses, ...plain } = p;
|
|
219
|
+
if (!dexProvider) {
|
|
220
|
+
const payload = {
|
|
221
|
+
...plain,
|
|
222
|
+
...(swapInstructions ? { provider: 'external', swapInstructions } : {}),
|
|
223
|
+
...(quote ? { quote } : {}),
|
|
224
|
+
...(addressLookupTableAddresses?.length ? { addressLookupTableAddresses } : {}),
|
|
225
|
+
};
|
|
226
|
+
return http.post('/v1/wallet/session/solana/dex/swap', payload);
|
|
227
|
+
}
|
|
228
|
+
const q = await dexProvider.quote(plain);
|
|
229
|
+
const swap = await dexProvider.swapInstructions({
|
|
230
|
+
inputMint: String(plain.inputMint),
|
|
231
|
+
outputMint: String(plain.outputMint),
|
|
232
|
+
amount: plain.amount,
|
|
233
|
+
slippageBps: plain.slippageBps,
|
|
234
|
+
userPublicKey: plain.vaultPda,
|
|
235
|
+
quote: q.raw,
|
|
236
|
+
});
|
|
237
|
+
return http.post('/v1/wallet/session/solana/dex/swap', {
|
|
238
|
+
...plain,
|
|
239
|
+
provider: 'external',
|
|
240
|
+
quote: {
|
|
241
|
+
inAmount: q.inAmount,
|
|
242
|
+
outAmount: q.outAmount,
|
|
243
|
+
otherAmountThreshold: q.otherAmountThreshold,
|
|
244
|
+
priceImpactPct: q.priceImpactPct,
|
|
245
|
+
slippageBps: q.slippageBps,
|
|
246
|
+
},
|
|
247
|
+
swapInstructions: swap.instructions,
|
|
248
|
+
...(swap.addressLookupTableAddresses?.length
|
|
249
|
+
? { addressLookupTableAddresses: swap.addressLookupTableAddresses }
|
|
250
|
+
: {}),
|
|
251
|
+
});
|
|
252
|
+
}
|
package/package.json
CHANGED