@candledottv/agent-sdk 0.4.11 → 0.6.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/README.md +196 -8
- package/dist/client.d.ts +478 -21
- package/dist/client.d.ts.map +1 -1
- package/dist/errors.d.ts +156 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +295 -50
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
3
3
|
|
|
4
4
|
// src/client.ts
|
|
5
5
|
import { keccak_256 as keccak_2562 } from "@noble/hashes/sha3";
|
|
6
|
+
import { base58 as base582 } from "@scure/base";
|
|
6
7
|
|
|
7
8
|
// src/authorization-signature.ts
|
|
8
9
|
import canonicalize from "canonicalize";
|
|
@@ -78,6 +79,7 @@ async function buildPrivyAuthorizationSignature(params) {
|
|
|
78
79
|
var BRIDGE_ERROR_CODES = ["BRIDGE_DESTINATION_MISSING", "RELAY_STEP_REFUSED", "BRIDGE_IN_FLIGHT"];
|
|
79
80
|
|
|
80
81
|
class CandleApiError extends Error {
|
|
82
|
+
legs;
|
|
81
83
|
code;
|
|
82
84
|
status;
|
|
83
85
|
retryable;
|
|
@@ -94,9 +96,12 @@ class CandleApiError extends Error {
|
|
|
94
96
|
feeRaw;
|
|
95
97
|
quoteAsset;
|
|
96
98
|
treasury;
|
|
99
|
+
reason;
|
|
100
|
+
retryAfter;
|
|
97
101
|
constructor(args) {
|
|
98
102
|
super(args.message);
|
|
99
103
|
this.name = "CandleApiError";
|
|
104
|
+
this.legs = args.legs;
|
|
100
105
|
this.code = args.code;
|
|
101
106
|
this.status = args.status;
|
|
102
107
|
this.retryable = args.retryable;
|
|
@@ -121,6 +126,45 @@ class CandleApiError extends Error {
|
|
|
121
126
|
this.quoteAsset = args.quoteAsset;
|
|
122
127
|
if (args.treasury !== undefined)
|
|
123
128
|
this.treasury = args.treasury;
|
|
129
|
+
if (args.reason !== undefined)
|
|
130
|
+
this.reason = args.reason;
|
|
131
|
+
if (args.retryAfter !== undefined)
|
|
132
|
+
this.retryAfter = args.retryAfter;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class TradeLegOutcomeError extends Error {
|
|
137
|
+
clientTradeId;
|
|
138
|
+
leg;
|
|
139
|
+
outcome;
|
|
140
|
+
txHash;
|
|
141
|
+
landedLegs;
|
|
142
|
+
cause;
|
|
143
|
+
constructor(args) {
|
|
144
|
+
super((args.cause instanceof Error ? args.cause.message + ". " : "") + "A Hood trade leg failed. Inspect txHash and landedLegs on chain; read getTradeJob(clientTradeId) or call trade with the same id and body for Candle's view. Never retry a retryable:false answer.", { cause: args.cause });
|
|
145
|
+
this.name = "TradeLegOutcomeError";
|
|
146
|
+
this.clientTradeId = args.clientTradeId;
|
|
147
|
+
this.leg = args.leg;
|
|
148
|
+
this.outcome = args.outcome;
|
|
149
|
+
this.txHash = args.txHash;
|
|
150
|
+
this.landedLegs = args.landedLegs;
|
|
151
|
+
this.cause = args.cause;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
class TradeLandedUnconfirmedError extends Error {
|
|
156
|
+
clientTradeId;
|
|
157
|
+
tradeTxHash;
|
|
158
|
+
feeTxHash;
|
|
159
|
+
stage = "executed";
|
|
160
|
+
cause;
|
|
161
|
+
constructor(args) {
|
|
162
|
+
super("The trade is on chain and not booked. Read getTradeJob(clientTradeId) before calling confirmTrade again with these hashes. If the row is failed, this id cannot be recorded with /confirm; retain the hashes and do not retry or rebuild.", { cause: args.cause });
|
|
163
|
+
this.name = "TradeLandedUnconfirmedError";
|
|
164
|
+
this.clientTradeId = args.clientTradeId;
|
|
165
|
+
this.tradeTxHash = args.tradeTxHash;
|
|
166
|
+
this.feeTxHash = args.feeTxHash;
|
|
167
|
+
this.cause = args.cause;
|
|
124
168
|
}
|
|
125
169
|
}
|
|
126
170
|
|
|
@@ -161,6 +205,51 @@ class JsonRpcError extends Error {
|
|
|
161
205
|
this.data = args.data;
|
|
162
206
|
}
|
|
163
207
|
}
|
|
208
|
+
function operationErrorMessage(cause, ids, flow) {
|
|
209
|
+
const original = cause instanceof Error ? cause.message : String(cause);
|
|
210
|
+
const isTrade = ids.clientTradeId !== undefined;
|
|
211
|
+
const idName = isTrade ? "clientTradeId" : "clientLaunchId";
|
|
212
|
+
const noun = isTrade ? "trade" : "launch";
|
|
213
|
+
const read = isTrade ? "getTradeJob()" : "getLaunchJob()";
|
|
214
|
+
const bundle = ids.bundleId !== undefined ? ` (bundleId ${ids.bundleId}, single-use: for logs only, never resubmitted)` : "";
|
|
215
|
+
const head = `${original} -- ${idName} ${ids.clientTradeId ?? ids.clientLaunchId}${bundle}: ` + "the request may or may not have reached Candle.";
|
|
216
|
+
const tail = `A call with a new id, or with none, is a second, independent ${noun}.`;
|
|
217
|
+
if (flow === "server-executed") {
|
|
218
|
+
return `${head} Read the job for this id (${read}), or retry the call with this same ${idName} and the same request. ${tail}`;
|
|
219
|
+
}
|
|
220
|
+
return `${head} This id identifies the job: read the job for this id (${read}), and check the wallet's recent ` + `transactions before sending again, because a call under the same id can sign and broadcast a second ` + `transaction. ${tail}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
class CandleOperationError extends Error {
|
|
224
|
+
constructor(args) {
|
|
225
|
+
super(operationErrorMessage(args.cause, args.ids, args.flow), { cause: args.cause });
|
|
226
|
+
this.name = "CandleOperationError";
|
|
227
|
+
Object.assign(this, definedIds(args.ids));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function definedIds(ids) {
|
|
231
|
+
return {
|
|
232
|
+
...typeof ids.clientTradeId === "string" ? { clientTradeId: ids.clientTradeId } : {},
|
|
233
|
+
...typeof ids.clientLaunchId === "string" ? { clientLaunchId: ids.clientLaunchId } : {},
|
|
234
|
+
...typeof ids.bundleId === "string" ? { bundleId: ids.bundleId } : {}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function operationIds(error) {
|
|
238
|
+
if (error instanceof CandleApiError || error instanceof JsonRpcError || error instanceof TradeLandedFeeLegError || error instanceof TradeLegOutcomeError || error instanceof TradeLandedUnconfirmedError || error instanceof CandleOperationError) {
|
|
239
|
+
return definedIds(error);
|
|
240
|
+
}
|
|
241
|
+
return {};
|
|
242
|
+
}
|
|
243
|
+
function withOperationIds(error, ids, flow) {
|
|
244
|
+
if (error instanceof CandleApiError || error instanceof JsonRpcError)
|
|
245
|
+
return Object.assign(error, definedIds(ids));
|
|
246
|
+
if (error instanceof TradeLandedFeeLegError || error instanceof TradeLegOutcomeError || error instanceof TradeLandedUnconfirmedError)
|
|
247
|
+
return error;
|
|
248
|
+
if (error instanceof CandleOperationError) {
|
|
249
|
+
return new CandleOperationError({ cause: error.cause, ids: { ...operationIds(error), ...definedIds(ids) }, flow });
|
|
250
|
+
}
|
|
251
|
+
return new CandleOperationError({ cause: error, ids, flow });
|
|
252
|
+
}
|
|
164
253
|
function envelopeError(body) {
|
|
165
254
|
if (typeof body !== "object" || body === null)
|
|
166
255
|
return null;
|
|
@@ -182,13 +271,16 @@ function envelopeError(body) {
|
|
|
182
271
|
message: error.message,
|
|
183
272
|
...typeof error.field === "string" ? { field: error.field } : {},
|
|
184
273
|
...typeof error.retryable === "boolean" ? { retryable: error.retryable } : {},
|
|
274
|
+
...Array.isArray(error.legs) ? { legs: error.legs } : {},
|
|
185
275
|
...typeof error.stage === "string" ? { stage: error.stage } : {},
|
|
186
276
|
...typeof error.signature === "string" ? { signature: error.signature } : {},
|
|
187
277
|
...typeof error.mint === "string" ? { mint: error.mint } : {},
|
|
188
278
|
...typeof error.recorded === "boolean" ? { recorded: error.recorded } : {},
|
|
189
279
|
...typeof error.feeRaw === "string" ? { feeRaw: error.feeRaw } : {},
|
|
190
280
|
...typeof error.quoteAsset === "string" ? { quoteAsset: error.quoteAsset } : {},
|
|
191
|
-
...typeof error.treasury === "string" ? { treasury: error.treasury } : {}
|
|
281
|
+
...typeof error.treasury === "string" ? { treasury: error.treasury } : {},
|
|
282
|
+
...typeof error.reason === "string" ? { reason: error.reason } : {},
|
|
283
|
+
...typeof error.retryAfter === "number" ? { retryAfter: error.retryAfter } : {}
|
|
192
284
|
};
|
|
193
285
|
}
|
|
194
286
|
function candleApiErrorFromResponse(status, bodyText) {
|
|
@@ -1074,6 +1166,45 @@ class CandleClient {
|
|
|
1074
1166
|
async verify(chain, mint) {
|
|
1075
1167
|
return this.requestJson("GET", `/api/v1/verify/${chain}/${encodeURIComponent(mint)}`);
|
|
1076
1168
|
}
|
|
1169
|
+
async getOwnProfile() {
|
|
1170
|
+
this.requireKey("getOwnProfile()");
|
|
1171
|
+
return this.requestJson("GET", "/api/v1/agent/keys/self/public-profile");
|
|
1172
|
+
}
|
|
1173
|
+
async updateOwnProfile(patch) {
|
|
1174
|
+
this.requireKey("updateOwnProfile()");
|
|
1175
|
+
return this.requestJson("PATCH", "/api/v1/agent/keys/self/public-profile", patch);
|
|
1176
|
+
}
|
|
1177
|
+
async uploadProfileImage(kind, bytes, contentType) {
|
|
1178
|
+
this.requireKey("uploadProfileImage()");
|
|
1179
|
+
const form = new FormData;
|
|
1180
|
+
form.set("kind", kind);
|
|
1181
|
+
form.set("image", new Blob([bytes], { type: contentType }), "profile.image");
|
|
1182
|
+
const headers = this.headers();
|
|
1183
|
+
delete headers["Content-Type"];
|
|
1184
|
+
const res = await this.fetchImpl(`${this.apiUrl}/api/v1/uploads/agent-profile-image`, {
|
|
1185
|
+
method: "POST",
|
|
1186
|
+
headers,
|
|
1187
|
+
body: form
|
|
1188
|
+
});
|
|
1189
|
+
return this.parseResponse(res);
|
|
1190
|
+
}
|
|
1191
|
+
async createPost(req) {
|
|
1192
|
+
this.requireKey("createPost()");
|
|
1193
|
+
return this.requestJson("POST", "/api/v1/agent/posts", req);
|
|
1194
|
+
}
|
|
1195
|
+
async deletePost(postId) {
|
|
1196
|
+
this.requireKey("deletePost()");
|
|
1197
|
+
return this.requestJson("DELETE", `/api/v1/agent/posts/${encodeURIComponent(postId)}`);
|
|
1198
|
+
}
|
|
1199
|
+
async listAgentPosts(profileId, options = {}) {
|
|
1200
|
+
const params = new URLSearchParams;
|
|
1201
|
+
if (options.limit !== undefined)
|
|
1202
|
+
params.set("limit", String(options.limit));
|
|
1203
|
+
if (options.cursor !== undefined)
|
|
1204
|
+
params.set("cursor", options.cursor);
|
|
1205
|
+
const body = await this.requestJson("GET", `/api/v1/agent/records/${encodeURIComponent(profileId)}/posts${params.size ? `?${params}` : ""}`);
|
|
1206
|
+
return { posts: body.posts, cursor: body.cursor ?? null };
|
|
1207
|
+
}
|
|
1077
1208
|
async getAgentProfile(idOrWallet) {
|
|
1078
1209
|
const body = await this.requestJson("GET", `/api/v1/users/${encodeURIComponent(idOrWallet)}/agent`);
|
|
1079
1210
|
return body.agent;
|
|
@@ -1105,25 +1236,33 @@ class CandleClient {
|
|
|
1105
1236
|
}
|
|
1106
1237
|
async launch(req) {
|
|
1107
1238
|
this.requireKey("launch()");
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1239
|
+
const clientLaunchId = req.clientLaunchId ?? generateClientLaunchId();
|
|
1240
|
+
const body = { ...req, clientLaunchId };
|
|
1241
|
+
return this.runOperation({ clientLaunchId }, "server-executed", async (op) => {
|
|
1242
|
+
let lastError;
|
|
1243
|
+
for (let attempt = 0;attempt <= this.maxRetries; attempt++) {
|
|
1244
|
+
if (attempt > 0)
|
|
1245
|
+
await sleep2(retryDelayMs(attempt - 1));
|
|
1246
|
+
try {
|
|
1247
|
+
const result = await this.requestJson("POST", "/api/v1/launch/headless", body, op);
|
|
1248
|
+
return { ...result, clientLaunchId };
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
if (!isRetryableLaunchFailure(error))
|
|
1251
|
+
throw error;
|
|
1252
|
+
lastError = error;
|
|
1253
|
+
}
|
|
1119
1254
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1255
|
+
throw lastError;
|
|
1256
|
+
});
|
|
1122
1257
|
}
|
|
1123
1258
|
async launchAsync(req) {
|
|
1124
1259
|
this.requireKey("launchAsync()");
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1260
|
+
const clientLaunchId = req.clientLaunchId ?? generateClientLaunchId();
|
|
1261
|
+
const body = { ...req, clientLaunchId, async: true };
|
|
1262
|
+
return this.runOperation({ clientLaunchId }, "server-executed", async (op) => {
|
|
1263
|
+
const accepted = await this.requestJson("POST", "/api/v1/launch/headless", body, op);
|
|
1264
|
+
return { ...accepted, clientLaunchId: accepted.clientLaunchId ?? clientLaunchId };
|
|
1265
|
+
});
|
|
1127
1266
|
}
|
|
1128
1267
|
async getLaunchJob(clientLaunchId) {
|
|
1129
1268
|
this.requireKey("getLaunchJob()");
|
|
@@ -1241,16 +1380,28 @@ class CandleClient {
|
|
|
1241
1380
|
return this.requestJson("POST", "/api/v1/launch/self/dry-run", req);
|
|
1242
1381
|
}
|
|
1243
1382
|
async buildSelfLaunch(req) {
|
|
1383
|
+
return this.postSelfLaunchBuild(req);
|
|
1384
|
+
}
|
|
1385
|
+
async postSelfLaunchBuild(req, op) {
|
|
1244
1386
|
this.requireKey("buildSelfLaunch()");
|
|
1245
|
-
return this.requestJson("POST", "/api/v1/launch/self/build", req);
|
|
1387
|
+
return this.requestJson("POST", "/api/v1/launch/self/build", req, op);
|
|
1246
1388
|
}
|
|
1247
1389
|
async confirmSelfLaunch(req) {
|
|
1248
1390
|
this.requireKey("confirmSelfLaunch()");
|
|
1249
|
-
|
|
1391
|
+
const result = await this.requestJson("POST", "/api/v1/launch/self/confirm", req);
|
|
1392
|
+
return { ...result, clientLaunchId: req.clientLaunchId };
|
|
1250
1393
|
}
|
|
1251
1394
|
async buildTrade(req) {
|
|
1395
|
+
return this.postTradeBuild(req);
|
|
1396
|
+
}
|
|
1397
|
+
async postTradeBuild(req, op) {
|
|
1252
1398
|
this.requireKey("buildTrade()");
|
|
1253
|
-
return this.requestJson("POST", "/api/v1/trade/agent/build", req);
|
|
1399
|
+
return this.requestJson("POST", "/api/v1/trade/agent/build", req, op);
|
|
1400
|
+
}
|
|
1401
|
+
async getTradeJob(clientTradeId) {
|
|
1402
|
+
this.requireKey("getTradeJob()");
|
|
1403
|
+
const body = await this.requestJson("GET", `/api/v1/trade/agent/jobs/${encodeURIComponent(clientTradeId)}`);
|
|
1404
|
+
return body.job;
|
|
1254
1405
|
}
|
|
1255
1406
|
async confirmTrade(req) {
|
|
1256
1407
|
this.requireKey("confirmTrade()");
|
|
@@ -1339,15 +1490,22 @@ class CandleClient {
|
|
|
1339
1490
|
...req.maxSlippageBps !== undefined ? { maxSlippageBps: req.maxSlippageBps } : {},
|
|
1340
1491
|
...req.quoteAsset !== undefined ? { quoteAsset: req.quoteAsset } : {}
|
|
1341
1492
|
};
|
|
1493
|
+
const flow = req.from === "main" ? "server-executed" : "caller-broadcast";
|
|
1494
|
+
return this.runOperation({ clientTradeId }, flow, async (op) => {
|
|
1495
|
+
const result = await this.tradeSteps(req, buildReq, op);
|
|
1496
|
+
return typeof result.clientTradeId === "string" ? result : { ...result, clientTradeId };
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
async tradeSteps(req, buildReq, op) {
|
|
1342
1500
|
if (req.from === "main") {
|
|
1343
|
-
const result = await this.
|
|
1501
|
+
const result = await this.postTradeBuild(buildReq, op);
|
|
1344
1502
|
if (result.status !== "executed") {
|
|
1345
1503
|
throw new Error(`trade({ from: "main" }) expected an executed result but got status "${result.status}"`);
|
|
1346
1504
|
}
|
|
1347
1505
|
return result;
|
|
1348
1506
|
}
|
|
1349
1507
|
const { linkedWalletId, privyWalletId } = req.from;
|
|
1350
|
-
const built = await this.
|
|
1508
|
+
const built = await this.postTradeBuild(buildReq, op);
|
|
1351
1509
|
if (built.status !== "built") {
|
|
1352
1510
|
return built;
|
|
1353
1511
|
}
|
|
@@ -1384,6 +1542,7 @@ class CandleClient {
|
|
|
1384
1542
|
if (built.artifacts.feeTransfer) {
|
|
1385
1543
|
legs.push({ kind: "feeTransfer", ...built.artifacts.feeTransfer });
|
|
1386
1544
|
}
|
|
1545
|
+
const landedLegs = [];
|
|
1387
1546
|
let tradeTxHash;
|
|
1388
1547
|
let feeTxHash;
|
|
1389
1548
|
for (let i = 0;i < legs.length; i++) {
|
|
@@ -1403,13 +1562,24 @@ class CandleClient {
|
|
|
1403
1562
|
feeData,
|
|
1404
1563
|
linkedWalletId,
|
|
1405
1564
|
privyWalletId
|
|
1406
|
-
}, { surfaceLegOutcome:
|
|
1565
|
+
}, { surfaceLegOutcome: true });
|
|
1407
1566
|
} catch (err) {
|
|
1408
1567
|
if (leg.kind === "feeTransfer" && tradeTxHash) {
|
|
1409
1568
|
throw tradeLandedFeeLegError({ clientTradeId: built.clientTradeId, tradeTxHash, err });
|
|
1410
1569
|
}
|
|
1411
|
-
|
|
1570
|
+
if (err instanceof EvmLegOutcome && err.txHash !== undefined || landedLegs.length > 0) {
|
|
1571
|
+
throw new TradeLegOutcomeError({
|
|
1572
|
+
clientTradeId: built.clientTradeId,
|
|
1573
|
+
leg: leg.kind,
|
|
1574
|
+
outcome: err instanceof EvmLegOutcome ? err.kind : "not-broadcast",
|
|
1575
|
+
txHash: err instanceof EvmLegOutcome ? err.txHash : undefined,
|
|
1576
|
+
landedLegs: [...landedLegs],
|
|
1577
|
+
cause: err instanceof EvmLegOutcome ? err.cause : err
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
throw err instanceof EvmLegOutcome ? err.cause : err;
|
|
1412
1581
|
}
|
|
1582
|
+
landedLegs.push({ leg: leg.kind, txHash });
|
|
1413
1583
|
if (leg.kind === "trade")
|
|
1414
1584
|
tradeTxHash = txHash;
|
|
1415
1585
|
if (leg.kind === "feeTransfer")
|
|
@@ -1418,29 +1588,38 @@ class CandleClient {
|
|
|
1418
1588
|
if (!tradeTxHash) {
|
|
1419
1589
|
throw new Error("trade(): Hood leg sequence completed without a trade leg");
|
|
1420
1590
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1591
|
+
try {
|
|
1592
|
+
return await this.confirmTrade({
|
|
1593
|
+
clientTradeId: built.clientTradeId,
|
|
1594
|
+
tradeTxHash,
|
|
1595
|
+
...feeTxHash ? { feeTxHash } : {}
|
|
1596
|
+
});
|
|
1597
|
+
} catch (cause) {
|
|
1598
|
+
throw new TradeLandedUnconfirmedError({ clientTradeId: built.clientTradeId, tradeTxHash, feeTxHash, cause });
|
|
1599
|
+
}
|
|
1426
1600
|
}
|
|
1427
1601
|
async selfLaunch(req) {
|
|
1428
1602
|
const { privyWalletId, ...launchReq } = req;
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1603
|
+
const clientLaunchId = launchReq.clientLaunchId ?? generateClientLaunchId();
|
|
1604
|
+
const body = { ...launchReq, clientLaunchId };
|
|
1605
|
+
return this.runOperation({ clientLaunchId }, "caller-broadcast", (op) => this.selfLaunchSteps(body, privyWalletId, op));
|
|
1606
|
+
}
|
|
1607
|
+
async selfLaunchSteps(body, privyWalletId, op) {
|
|
1608
|
+
const built = await this.postSelfLaunchBuild(body, op);
|
|
1434
1609
|
if (typeof built.transaction === "string") {
|
|
1435
1610
|
let unsignedTransactionBase64 = built.transaction;
|
|
1436
1611
|
let clientLaunchId = built.clientLaunchId;
|
|
1612
|
+
let signed;
|
|
1437
1613
|
for (let attempt = 0;attempt <= MAX_BLOCKHASH_REBUILDS; attempt++) {
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1614
|
+
if (signed?.forTransaction !== unsignedTransactionBase64) {
|
|
1615
|
+
const result = await this.signLinkedTransaction({
|
|
1616
|
+
linkedWalletId: body.linkedWalletId,
|
|
1617
|
+
privyWalletId,
|
|
1618
|
+
chain: "solana",
|
|
1619
|
+
unsignedTransactionBase64
|
|
1620
|
+
});
|
|
1621
|
+
signed = { forTransaction: unsignedTransactionBase64, ...result };
|
|
1622
|
+
}
|
|
1444
1623
|
try {
|
|
1445
1624
|
const signature = await this.broadcastSignedTransaction("solana", signed.signedTransaction, signed.encoding);
|
|
1446
1625
|
return this.confirmSelfLaunch({ clientLaunchId, signature });
|
|
@@ -1449,7 +1628,20 @@ class CandleClient {
|
|
|
1449
1628
|
throw error;
|
|
1450
1629
|
if (attempt === MAX_BLOCKHASH_REBUILDS)
|
|
1451
1630
|
throw withRpcLagHint(error);
|
|
1452
|
-
|
|
1631
|
+
let rebuilt;
|
|
1632
|
+
try {
|
|
1633
|
+
rebuilt = await this.postSelfLaunchBuild(body, op);
|
|
1634
|
+
} catch (buildError) {
|
|
1635
|
+
const reason = buildError instanceof CandleApiError ? buildError.reason : undefined;
|
|
1636
|
+
if (reason === "prior_attempt_in_flight")
|
|
1637
|
+
continue;
|
|
1638
|
+
if (reason === "prior_attempt_on_chain") {
|
|
1639
|
+
const signature = solanaSignedTxSignature(signed.signedTransaction, signed.encoding);
|
|
1640
|
+
if (signature)
|
|
1641
|
+
return this.confirmSelfLaunch({ clientLaunchId, signature });
|
|
1642
|
+
}
|
|
1643
|
+
throw buildError;
|
|
1644
|
+
}
|
|
1453
1645
|
if (typeof rebuilt.transaction !== "string")
|
|
1454
1646
|
throw error;
|
|
1455
1647
|
unsignedTransactionBase64 = rebuilt.transaction;
|
|
@@ -1502,8 +1694,13 @@ class CandleClient {
|
|
|
1502
1694
|
}
|
|
1503
1695
|
async buildAtomicLaunch(req) {
|
|
1504
1696
|
this.requireKey("buildAtomicLaunch()");
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1697
|
+
const clientLaunchId = req.clientLaunchId ?? generateClientLaunchId();
|
|
1698
|
+
const body = { ...req, clientLaunchId };
|
|
1699
|
+
return this.runOperation({ clientLaunchId }, "server-executed", (op) => this.postAtomicBuild(body, clientLaunchId, op));
|
|
1700
|
+
}
|
|
1701
|
+
async postAtomicBuild(body, clientLaunchId, op) {
|
|
1702
|
+
const built = await this.requestJson("POST", "/api/v1/launch/atomic/build", body, op);
|
|
1703
|
+
return { ...built, clientLaunchId };
|
|
1507
1704
|
}
|
|
1508
1705
|
async submitAtomicLaunch(req) {
|
|
1509
1706
|
this.requireKey("submitAtomicLaunch()");
|
|
@@ -1526,14 +1723,24 @@ class CandleClient {
|
|
|
1526
1723
|
throw new Error(`submitAtomicLaunch(): unexpected 200 response shape: ${text}`);
|
|
1527
1724
|
}
|
|
1528
1725
|
async launchAtomic(req) {
|
|
1726
|
+
this.requireKey("buildAtomicLaunch()");
|
|
1529
1727
|
const { payer, firstBuys, ...launchFields } = req;
|
|
1728
|
+
const clientLaunchId = launchFields.clientLaunchId ?? generateClientLaunchId();
|
|
1530
1729
|
const buildReq = {
|
|
1531
1730
|
...launchFields,
|
|
1532
|
-
clientLaunchId
|
|
1731
|
+
clientLaunchId,
|
|
1533
1732
|
payer: toAtomicWirePayer(payer),
|
|
1534
1733
|
firstBuys: firstBuys.map((leg) => ({ payer: toAtomicWirePayer(leg.payer), amountRaw: leg.amountRaw }))
|
|
1535
1734
|
};
|
|
1536
|
-
|
|
1735
|
+
return this.runOperation({ clientLaunchId }, "server-executed", async (op) => {
|
|
1736
|
+
const outcome = await this.launchAtomicSteps(req, buildReq, clientLaunchId, op);
|
|
1737
|
+
return { ...outcome, clientLaunchId };
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
async launchAtomicSteps(req, buildReq, clientLaunchId, op) {
|
|
1741
|
+
const { payer, firstBuys } = req;
|
|
1742
|
+
const built = await this.postAtomicBuild(buildReq, clientLaunchId, op);
|
|
1743
|
+
op.ids.bundleId = built.bundleId;
|
|
1537
1744
|
const signedTxsBase64 = [];
|
|
1538
1745
|
for (const leg of built.legs) {
|
|
1539
1746
|
if (leg.signer !== "client")
|
|
@@ -1605,7 +1812,7 @@ class CandleClient {
|
|
|
1605
1812
|
if (!isDefiniteBroadcastRejection(err)) {
|
|
1606
1813
|
throw new EvmLegOutcome({ kind: "unknown", txHash: predicted, cause: err });
|
|
1607
1814
|
}
|
|
1608
|
-
throw err;
|
|
1815
|
+
throw new EvmLegOutcome({ kind: "not-broadcast", txHash: predicted, cause: err });
|
|
1609
1816
|
}
|
|
1610
1817
|
try {
|
|
1611
1818
|
await waitForReceipt(params.rpc, txHash, evmReceiptWaitForTest ?? {});
|
|
@@ -1869,12 +2076,25 @@ class CandleClient {
|
|
|
1869
2076
|
headers["x-api-key"] = this.apiKey;
|
|
1870
2077
|
return headers;
|
|
1871
2078
|
}
|
|
1872
|
-
async
|
|
1873
|
-
const
|
|
2079
|
+
async runOperation(ids, flow, steps) {
|
|
2080
|
+
const op = { sent: false, ids: { ...ids } };
|
|
2081
|
+
try {
|
|
2082
|
+
return await steps(op);
|
|
2083
|
+
} catch (error) {
|
|
2084
|
+
if (!op.sent)
|
|
2085
|
+
throw error;
|
|
2086
|
+
throw withOperationIds(error, op.ids, flow);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
async requestJson(method, path, body, op) {
|
|
2090
|
+
const init = {
|
|
1874
2091
|
method,
|
|
1875
2092
|
headers: this.headers({ json: body !== undefined }),
|
|
1876
2093
|
...body !== undefined ? { body: JSON.stringify(body) } : {}
|
|
1877
|
-
}
|
|
2094
|
+
};
|
|
2095
|
+
if (op)
|
|
2096
|
+
op.sent = true;
|
|
2097
|
+
const res = await this.fetchImpl(`${this.apiUrl}${path}`, init);
|
|
1878
2098
|
return this.parseResponse(res);
|
|
1879
2099
|
}
|
|
1880
2100
|
async parseResponse(res) {
|
|
@@ -1949,7 +2169,7 @@ function tradeLandedFeeLegError(args) {
|
|
|
1949
2169
|
tradeTxHash: args.tradeTxHash,
|
|
1950
2170
|
cause: args.err.cause,
|
|
1951
2171
|
feeOutcome: args.err.kind,
|
|
1952
|
-
...args.err.txHash !== undefined ? { feeTxHash: args.err.txHash } : {}
|
|
2172
|
+
...args.err.kind !== "not-broadcast" && args.err.txHash !== undefined ? { feeTxHash: args.err.txHash } : {}
|
|
1953
2173
|
});
|
|
1954
2174
|
}
|
|
1955
2175
|
return new TradeLandedFeeLegError({
|
|
@@ -1959,6 +2179,27 @@ function tradeLandedFeeLegError(args) {
|
|
|
1959
2179
|
feeOutcome: "not-broadcast"
|
|
1960
2180
|
});
|
|
1961
2181
|
}
|
|
2182
|
+
function solanaSignedTxSignature(signedTransaction, encoding) {
|
|
2183
|
+
const enc = encoding.toLowerCase();
|
|
2184
|
+
let bytes;
|
|
2185
|
+
try {
|
|
2186
|
+
if (enc === "base64")
|
|
2187
|
+
bytes = fromBase64(signedTransaction);
|
|
2188
|
+
else if (enc === "base58")
|
|
2189
|
+
bytes = base582.decode(signedTransaction);
|
|
2190
|
+
else
|
|
2191
|
+
return;
|
|
2192
|
+
} catch {
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
const count = bytes[0];
|
|
2196
|
+
if (count === undefined || count === 0 || count >= 128 || bytes.length < 1 + count * 64)
|
|
2197
|
+
return;
|
|
2198
|
+
const first = bytes.subarray(1, 65);
|
|
2199
|
+
if (first.every((byte) => byte === 0))
|
|
2200
|
+
return;
|
|
2201
|
+
return base582.encode(first);
|
|
2202
|
+
}
|
|
1962
2203
|
function evmSignedTxHash(signedTransaction, encoding) {
|
|
1963
2204
|
const enc = encoding.toLowerCase();
|
|
1964
2205
|
let bytes;
|
|
@@ -1997,7 +2238,7 @@ function generateClientLaunchId() {
|
|
|
1997
2238
|
function generateClientTradeId() {
|
|
1998
2239
|
return generateSdkId();
|
|
1999
2240
|
}
|
|
2000
|
-
var SDK_VERSION = "0.
|
|
2241
|
+
var SDK_VERSION = "0.6.0";
|
|
2001
2242
|
var sdkUpdateWarned = false;
|
|
2002
2243
|
function noteLatestSdkVersion(value) {
|
|
2003
2244
|
if (sdkUpdateWarned || !value || !/^\d+\.\d+\.\d+$/.test(value))
|
|
@@ -2361,11 +2602,14 @@ export {
|
|
|
2361
2602
|
planPromotionLine,
|
|
2362
2603
|
planLabel,
|
|
2363
2604
|
perpsDepositProblem,
|
|
2605
|
+
operationIds,
|
|
2364
2606
|
isSolanaRpcErrorData,
|
|
2365
2607
|
hyperliquidActionHash,
|
|
2366
2608
|
generateSignerKeypair,
|
|
2367
2609
|
formatPlanBps,
|
|
2368
2610
|
encryptWalletKeyForImport,
|
|
2611
|
+
TradeLegOutcomeError,
|
|
2612
|
+
TradeLandedUnconfirmedError,
|
|
2369
2613
|
TradeLandedFeeLegError,
|
|
2370
2614
|
PLAN_CAPABILITY_NOTE,
|
|
2371
2615
|
PLAN_CAPABILITY_LABELS,
|
|
@@ -2375,6 +2619,7 @@ export {
|
|
|
2375
2619
|
HYPERLIQUID_EXCHANGE_URLS,
|
|
2376
2620
|
HYPERLIQUID_ALLOWED_ACTION_TYPES,
|
|
2377
2621
|
EncryptedFileSecretStore,
|
|
2622
|
+
CandleOperationError,
|
|
2378
2623
|
CandleClient,
|
|
2379
2624
|
CandleApiError,
|
|
2380
2625
|
CANDLE_HYPERLIQUID_BUILDER_ADDRESS,
|
package/package.json
CHANGED