@acta-markets/ts-sdk 0.0.1-beta
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 +308 -0
- package/dist/actaClient.d.ts +46 -0
- package/dist/actaClient.js +99 -0
- package/dist/chain/client.d.ts +65 -0
- package/dist/chain/client.js +82 -0
- package/dist/chain/fetch.d.ts +204 -0
- package/dist/chain/fetch.js +392 -0
- package/dist/chain/fetch.test.d.ts +1 -0
- package/dist/chain/fetch.test.js +158 -0
- package/dist/chain/flows/index.d.ts +1 -0
- package/dist/chain/flows/index.js +1 -0
- package/dist/chain/flows/openPosition.d.ts +48 -0
- package/dist/chain/flows/openPosition.js +78 -0
- package/dist/chain/flows/openPosition.test.d.ts +1 -0
- package/dist/chain/flows/openPosition.test.js +43 -0
- package/dist/chain/helpers.d.ts +16 -0
- package/dist/chain/helpers.js +60 -0
- package/dist/chain/index.d.ts +9 -0
- package/dist/chain/index.js +9 -0
- package/dist/chain/instructions.admin.d.ts +44 -0
- package/dist/chain/instructions.admin.js +113 -0
- package/dist/chain/instructions.d.ts +5 -0
- package/dist/chain/instructions.js +5 -0
- package/dist/chain/instructions.maker.d.ts +34 -0
- package/dist/chain/instructions.maker.js +86 -0
- package/dist/chain/instructions.market.d.ts +39 -0
- package/dist/chain/instructions.market.js +107 -0
- package/dist/chain/instructions.oracle.d.ts +38 -0
- package/dist/chain/instructions.oracle.js +63 -0
- package/dist/chain/instructions.position.d.ts +82 -0
- package/dist/chain/instructions.position.js +240 -0
- package/dist/chain/instructions.resolve.test.d.ts +1 -0
- package/dist/chain/instructions.resolve.test.js +32 -0
- package/dist/chain/instructions.shared.d.ts +23 -0
- package/dist/chain/instructions.shared.js +55 -0
- package/dist/chain/orders.d.ts +40 -0
- package/dist/chain/orders.js +117 -0
- package/dist/chain/orders.test.d.ts +1 -0
- package/dist/chain/orders.test.js +67 -0
- package/dist/chain/signers.d.ts +9 -0
- package/dist/chain/signers.js +19 -0
- package/dist/chain/signers.test.d.ts +1 -0
- package/dist/chain/signers.test.js +13 -0
- package/dist/chain/token.d.ts +59 -0
- package/dist/chain/token.js +136 -0
- package/dist/chain/token.test.d.ts +1 -0
- package/dist/chain/token.test.js +69 -0
- package/dist/chain/tx.d.ts +72 -0
- package/dist/chain/tx.js +97 -0
- package/dist/cjs/actaClient.js +103 -0
- package/dist/cjs/chain/client.js +119 -0
- package/dist/cjs/chain/fetch.js +431 -0
- package/dist/cjs/chain/fetch.test.js +160 -0
- package/dist/cjs/chain/flows/index.js +17 -0
- package/dist/cjs/chain/flows/openPosition.js +81 -0
- package/dist/cjs/chain/flows/openPosition.test.js +45 -0
- package/dist/cjs/chain/helpers.js +67 -0
- package/dist/cjs/chain/index.js +48 -0
- package/dist/cjs/chain/instructions.admin.js +119 -0
- package/dist/cjs/chain/instructions.js +21 -0
- package/dist/cjs/chain/instructions.maker.js +92 -0
- package/dist/cjs/chain/instructions.market.js +112 -0
- package/dist/cjs/chain/instructions.oracle.js +70 -0
- package/dist/cjs/chain/instructions.position.js +247 -0
- package/dist/cjs/chain/instructions.resolve.test.js +34 -0
- package/dist/cjs/chain/instructions.shared.js +64 -0
- package/dist/cjs/chain/orders.js +126 -0
- package/dist/cjs/chain/orders.test.js +69 -0
- package/dist/cjs/chain/signers.js +22 -0
- package/dist/cjs/chain/signers.test.js +15 -0
- package/dist/cjs/chain/token.js +147 -0
- package/dist/cjs/chain/token.test.js +71 -0
- package/dist/cjs/chain/tx.js +103 -0
- package/dist/cjs/constants.js +6 -0
- package/dist/cjs/constants.test.js +31 -0
- package/dist/cjs/events.js +75 -0
- package/dist/cjs/events.test.js +384 -0
- package/dist/cjs/generated/accounts/config.js +79 -0
- package/dist/cjs/generated/accounts/index.js +28 -0
- package/dist/cjs/generated/accounts/maker.js +71 -0
- package/dist/cjs/generated/accounts/market.js +93 -0
- package/dist/cjs/generated/accounts/oracle.js +85 -0
- package/dist/cjs/generated/accounts/position.js +89 -0
- package/dist/cjs/generated/errors/actaContract.js +154 -0
- package/dist/cjs/generated/errors/index.js +24 -0
- package/dist/cjs/generated/index.js +28 -0
- package/dist/cjs/generated/instructions/changeOracleSource.js +81 -0
- package/dist/cjs/generated/instructions/closeMarket.js +83 -0
- package/dist/cjs/generated/instructions/closeOracle.js +79 -0
- package/dist/cjs/generated/instructions/createMarket.js +116 -0
- package/dist/cjs/generated/instructions/createOracle.js +99 -0
- package/dist/cjs/generated/instructions/depositFundsToPosition.js +101 -0
- package/dist/cjs/generated/instructions/depositPremium.js +96 -0
- package/dist/cjs/generated/instructions/finalizeMarket.js +91 -0
- package/dist/cjs/generated/instructions/index.js +43 -0
- package/dist/cjs/generated/instructions/initializeConfig.js +108 -0
- package/dist/cjs/generated/instructions/liquidatePosition.js +107 -0
- package/dist/cjs/generated/instructions/openPosition.js +154 -0
- package/dist/cjs/generated/instructions/registerMaker.js +90 -0
- package/dist/cjs/generated/instructions/setOracleConfig.js +80 -0
- package/dist/cjs/generated/instructions/settlePosition.js +104 -0
- package/dist/cjs/generated/instructions/topupFeeFund.js +96 -0
- package/dist/cjs/generated/instructions/updateConfig.js +86 -0
- package/dist/cjs/generated/instructions/updateMakerQuoteSigning.js +79 -0
- package/dist/cjs/generated/instructions/updateMarketOracles.js +96 -0
- package/dist/cjs/generated/instructions/updateOraclePrice.js +89 -0
- package/dist/cjs/generated/instructions/withdrawFromFeeFund.js +96 -0
- package/dist/cjs/generated/instructions/withdrawPremium.js +96 -0
- package/dist/cjs/generated/programs/actaContract.js +108 -0
- package/dist/cjs/generated/programs/index.js +24 -0
- package/dist/cjs/generated/shared/index.js +94 -0
- package/dist/cjs/generated/types/actaEvent.js +342 -0
- package/dist/cjs/generated/types/eventKind.js +45 -0
- package/dist/cjs/generated/types/index.js +27 -0
- package/dist/cjs/generated/types/positionStatus.js +31 -0
- package/dist/cjs/generated/types/positionType.js +28 -0
- package/dist/cjs/idl/acta_contract.json +2329 -0
- package/dist/cjs/idl/hash.js +4 -0
- package/dist/cjs/index.js +84 -0
- package/dist/cjs/nonce.js +93 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/types/index.js +18 -0
- package/dist/cjs/types/orderId.js +40 -0
- package/dist/cjs/types/orders.js +10 -0
- package/dist/cjs/ws/apy.js +106 -0
- package/dist/cjs/ws/apy.test.js +29 -0
- package/dist/cjs/ws/auth.js +104 -0
- package/dist/cjs/ws/client.handshake.integration.test.js +69 -0
- package/dist/cjs/ws/client.js +861 -0
- package/dist/cjs/ws/client.test.js +230 -0
- package/dist/cjs/ws/discovery.js +48 -0
- package/dist/cjs/ws/flows.js +101 -0
- package/dist/cjs/ws/flows.test.js +85 -0
- package/dist/cjs/ws/index.js +23 -0
- package/dist/cjs/ws/sponsoredTx.js +95 -0
- package/dist/cjs/ws/types.js +14 -0
- package/dist/cjs/ws/wirePolicy.js +34 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +3 -0
- package/dist/constants.test.d.ts +1 -0
- package/dist/constants.test.js +29 -0
- package/dist/events.d.ts +15 -0
- package/dist/events.js +64 -0
- package/dist/events.test.d.ts +1 -0
- package/dist/events.test.js +382 -0
- package/dist/generated/accounts/config.d.ts +47 -0
- package/dist/generated/accounts/config.js +68 -0
- package/dist/generated/accounts/index.d.ts +12 -0
- package/dist/generated/accounts/index.js +12 -0
- package/dist/generated/accounts/maker.d.ts +39 -0
- package/dist/generated/accounts/maker.js +60 -0
- package/dist/generated/accounts/market.d.ts +61 -0
- package/dist/generated/accounts/market.js +82 -0
- package/dist/generated/accounts/oracle.d.ts +53 -0
- package/dist/generated/accounts/oracle.js +74 -0
- package/dist/generated/accounts/position.d.ts +57 -0
- package/dist/generated/accounts/position.js +78 -0
- package/dist/generated/errors/actaContract.d.ts +103 -0
- package/dist/generated/errors/actaContract.js +149 -0
- package/dist/generated/errors/index.d.ts +8 -0
- package/dist/generated/errors/index.js +8 -0
- package/dist/generated/index.d.ts +12 -0
- package/dist/generated/index.js +12 -0
- package/dist/generated/instructions/changeOracleSource.d.ts +50 -0
- package/dist/generated/instructions/changeOracleSource.js +72 -0
- package/dist/generated/instructions/closeMarket.d.ts +58 -0
- package/dist/generated/instructions/closeMarket.js +74 -0
- package/dist/generated/instructions/closeOracle.d.ts +48 -0
- package/dist/generated/instructions/closeOracle.js +70 -0
- package/dist/generated/instructions/createMarket.d.ts +90 -0
- package/dist/generated/instructions/createMarket.js +107 -0
- package/dist/generated/instructions/createOracle.d.ts +71 -0
- package/dist/generated/instructions/createOracle.js +90 -0
- package/dist/generated/instructions/depositFundsToPosition.d.ts +73 -0
- package/dist/generated/instructions/depositFundsToPosition.js +92 -0
- package/dist/generated/instructions/depositPremium.d.ts +62 -0
- package/dist/generated/instructions/depositPremium.js +87 -0
- package/dist/generated/instructions/finalizeMarket.d.ts +63 -0
- package/dist/generated/instructions/finalizeMarket.js +82 -0
- package/dist/generated/instructions/index.d.ts +27 -0
- package/dist/generated/instructions/index.js +27 -0
- package/dist/generated/instructions/initializeConfig.d.ts +75 -0
- package/dist/generated/instructions/initializeConfig.js +99 -0
- package/dist/generated/instructions/liquidatePosition.d.ts +78 -0
- package/dist/generated/instructions/liquidatePosition.js +98 -0
- package/dist/generated/instructions/openPosition.d.ts +130 -0
- package/dist/generated/instructions/openPosition.js +145 -0
- package/dist/generated/instructions/registerMaker.d.ts +57 -0
- package/dist/generated/instructions/registerMaker.js +81 -0
- package/dist/generated/instructions/setOracleConfig.d.ts +53 -0
- package/dist/generated/instructions/setOracleConfig.js +71 -0
- package/dist/generated/instructions/settlePosition.d.ts +78 -0
- package/dist/generated/instructions/settlePosition.js +95 -0
- package/dist/generated/instructions/topupFeeFund.d.ts +67 -0
- package/dist/generated/instructions/topupFeeFund.js +87 -0
- package/dist/generated/instructions/updateConfig.d.ts +58 -0
- package/dist/generated/instructions/updateConfig.js +77 -0
- package/dist/generated/instructions/updateMakerQuoteSigning.d.ts +47 -0
- package/dist/generated/instructions/updateMakerQuoteSigning.js +70 -0
- package/dist/generated/instructions/updateMarketOracles.d.ts +65 -0
- package/dist/generated/instructions/updateMarketOracles.js +87 -0
- package/dist/generated/instructions/updateOraclePrice.d.ts +55 -0
- package/dist/generated/instructions/updateOraclePrice.js +80 -0
- package/dist/generated/instructions/withdrawFromFeeFund.d.ts +62 -0
- package/dist/generated/instructions/withdrawFromFeeFund.js +87 -0
- package/dist/generated/instructions/withdrawPremium.d.ts +62 -0
- package/dist/generated/instructions/withdrawPremium.js +87 -0
- package/dist/generated/programs/actaContract.d.ts +83 -0
- package/dist/generated/programs/actaContract.js +104 -0
- package/dist/generated/programs/index.d.ts +8 -0
- package/dist/generated/programs/index.js +8 -0
- package/dist/generated/shared/index.d.ts +49 -0
- package/dist/generated/shared/index.js +86 -0
- package/dist/generated/types/actaEvent.d.ts +249 -0
- package/dist/generated/types/actaEvent.js +335 -0
- package/dist/generated/types/eventKind.d.ts +33 -0
- package/dist/generated/types/eventKind.js +39 -0
- package/dist/generated/types/index.d.ts +11 -0
- package/dist/generated/types/index.js +11 -0
- package/dist/generated/types/positionStatus.d.ts +19 -0
- package/dist/generated/types/positionStatus.js +25 -0
- package/dist/generated/types/positionType.d.ts +16 -0
- package/dist/generated/types/positionType.js +22 -0
- package/dist/idl/acta_contract.json +2329 -0
- package/dist/idl/hash.d.ts +1 -0
- package/dist/idl/hash.js +1 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +34 -0
- package/dist/nonce.d.ts +29 -0
- package/dist/nonce.js +89 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/orderId.d.ts +13 -0
- package/dist/types/orderId.js +35 -0
- package/dist/types/orders.d.ts +9 -0
- package/dist/types/orders.js +9 -0
- package/dist/ws/apy.d.ts +79 -0
- package/dist/ws/apy.js +98 -0
- package/dist/ws/apy.test.d.ts +1 -0
- package/dist/ws/apy.test.js +27 -0
- package/dist/ws/auth.d.ts +67 -0
- package/dist/ws/auth.js +98 -0
- package/dist/ws/client.d.ts +263 -0
- package/dist/ws/client.handshake.integration.test.d.ts +1 -0
- package/dist/ws/client.handshake.integration.test.js +64 -0
- package/dist/ws/client.js +857 -0
- package/dist/ws/client.test.d.ts +1 -0
- package/dist/ws/client.test.js +228 -0
- package/dist/ws/discovery.d.ts +13 -0
- package/dist/ws/discovery.js +44 -0
- package/dist/ws/flows.d.ts +44 -0
- package/dist/ws/flows.js +96 -0
- package/dist/ws/flows.test.d.ts +1 -0
- package/dist/ws/flows.test.js +83 -0
- package/dist/ws/index.d.ts +7 -0
- package/dist/ws/index.js +7 -0
- package/dist/ws/sponsoredTx.d.ts +39 -0
- package/dist/ws/sponsoredTx.js +92 -0
- package/dist/ws/types.d.ts +900 -0
- package/dist/ws/types.js +13 -0
- package/dist/ws/wirePolicy.d.ts +12 -0
- package/dist/ws/wirePolicy.js +30 -0
- package/package.json +87 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { ActaWsClient } from "./client";
|
|
2
|
+
const WS_CONNECTING = 0;
|
|
3
|
+
const WS_OPEN = 1;
|
|
4
|
+
const WS_CLOSED = 3;
|
|
5
|
+
const WELCOME_MESSAGE = {
|
|
6
|
+
type: "Welcome",
|
|
7
|
+
data: {
|
|
8
|
+
protocol_version: "1.0.0",
|
|
9
|
+
server_version: "test",
|
|
10
|
+
min_supported_version: "1.0.0",
|
|
11
|
+
enabled_features: [],
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
class MockWebSocket {
|
|
15
|
+
url;
|
|
16
|
+
readyState = WS_CONNECTING;
|
|
17
|
+
onopen = null;
|
|
18
|
+
onclose = null;
|
|
19
|
+
onerror = null;
|
|
20
|
+
onmessage = null;
|
|
21
|
+
sent = [];
|
|
22
|
+
constructor(url) {
|
|
23
|
+
this.url = url;
|
|
24
|
+
}
|
|
25
|
+
send(data) {
|
|
26
|
+
this.sent.push(data);
|
|
27
|
+
}
|
|
28
|
+
close(code, reason) {
|
|
29
|
+
this.readyState = WS_CLOSED;
|
|
30
|
+
this.onclose?.({ code, reason });
|
|
31
|
+
}
|
|
32
|
+
triggerOpen() {
|
|
33
|
+
this.readyState = WS_OPEN;
|
|
34
|
+
this.onopen?.({});
|
|
35
|
+
}
|
|
36
|
+
triggerClose(code = 1000, reason = "") {
|
|
37
|
+
this.readyState = WS_CLOSED;
|
|
38
|
+
this.onclose?.({ code, reason });
|
|
39
|
+
}
|
|
40
|
+
triggerError(value) {
|
|
41
|
+
this.onerror?.(value);
|
|
42
|
+
}
|
|
43
|
+
triggerMessage(message) {
|
|
44
|
+
this.onmessage?.({ data: JSON.stringify(message) });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function parseClientMessage(payload) {
|
|
48
|
+
return JSON.parse(payload);
|
|
49
|
+
}
|
|
50
|
+
function makeAuthProvider(pubkey = "pubkey", signature = "signature") {
|
|
51
|
+
return {
|
|
52
|
+
getPublicKey: jest.fn().mockResolvedValue(pubkey),
|
|
53
|
+
signChallenge: jest.fn().mockResolvedValue(signature),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function makeHarness(overrides = {}) {
|
|
57
|
+
let currentSocket = null;
|
|
58
|
+
const client = new ActaWsClient({
|
|
59
|
+
url: "ws://localhost:1234",
|
|
60
|
+
role: "taker",
|
|
61
|
+
autoReconnect: false,
|
|
62
|
+
...overrides,
|
|
63
|
+
makeSocket: (url) => {
|
|
64
|
+
currentSocket = new MockWebSocket(url);
|
|
65
|
+
return currentSocket;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
client,
|
|
70
|
+
socket: () => {
|
|
71
|
+
if (!currentSocket) {
|
|
72
|
+
throw new Error("Socket is not initialized");
|
|
73
|
+
}
|
|
74
|
+
return currentSocket;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function flushMicrotasks() {
|
|
79
|
+
await Promise.resolve();
|
|
80
|
+
await Promise.resolve();
|
|
81
|
+
}
|
|
82
|
+
describe("ActaWsClient", () => {
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
jest.restoreAllMocks();
|
|
85
|
+
jest.useRealTimers();
|
|
86
|
+
});
|
|
87
|
+
it("uses 1.0.0 protocolVersion by default in Hello", () => {
|
|
88
|
+
const { client, socket } = makeHarness();
|
|
89
|
+
client.connectAnonymous();
|
|
90
|
+
const ws = socket();
|
|
91
|
+
ws.triggerOpen();
|
|
92
|
+
expect(ws.sent).toHaveLength(1);
|
|
93
|
+
const hello = parseClientMessage(ws.sent[0]);
|
|
94
|
+
expect(hello.type).toBe("Hello");
|
|
95
|
+
if (hello.type === "Hello") {
|
|
96
|
+
expect(hello.data.protocol_version).toBe("1.0.0");
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it("connectAndAuthenticate sends StartAuth after Welcome", async () => {
|
|
100
|
+
const { client, socket } = makeHarness();
|
|
101
|
+
const auth = makeAuthProvider("WalletPubkey", "WalletSignature");
|
|
102
|
+
client.connectAndAuthenticate(auth);
|
|
103
|
+
const ws = socket();
|
|
104
|
+
ws.triggerOpen();
|
|
105
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
106
|
+
await flushMicrotasks();
|
|
107
|
+
const sentTypes = ws.sent.map((payload) => parseClientMessage(payload).type);
|
|
108
|
+
expect(sentTypes).toEqual(["Hello", "StartAuth"]);
|
|
109
|
+
ws.triggerMessage({
|
|
110
|
+
type: "AuthRequest",
|
|
111
|
+
data: { challenge: "challenge-text" },
|
|
112
|
+
});
|
|
113
|
+
await flushMicrotasks();
|
|
114
|
+
const authChallenge = parseClientMessage(ws.sent[2]);
|
|
115
|
+
expect(authChallenge.type).toBe("AuthChallenge");
|
|
116
|
+
if (authChallenge.type === "AuthChallenge") {
|
|
117
|
+
expect(authChallenge.data.pubkey).toBe("WalletPubkey");
|
|
118
|
+
expect(authChallenge.data.signature).toBe("WalletSignature");
|
|
119
|
+
expect(authChallenge.data.challenge).toBe("challenge-text");
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
it("drop_oldest policy keeps the latest queued messages", () => {
|
|
123
|
+
const { client, socket } = makeHarness({
|
|
124
|
+
maxPendingMessages: 2,
|
|
125
|
+
pendingMessagesOverflowPolicy: "drop_oldest",
|
|
126
|
+
});
|
|
127
|
+
client.connectAnonymous();
|
|
128
|
+
const ws = socket();
|
|
129
|
+
ws.triggerOpen();
|
|
130
|
+
client.getMarkets();
|
|
131
|
+
client.getTokens();
|
|
132
|
+
client.getExpiries();
|
|
133
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
134
|
+
const flushedTypes = ws.sent
|
|
135
|
+
.slice(1)
|
|
136
|
+
.map((x) => parseClientMessage(x).type);
|
|
137
|
+
expect(flushedTypes).toEqual(["GetTokens", "GetExpiries"]);
|
|
138
|
+
});
|
|
139
|
+
it("drop_newest policy ignores newest message when queue is full", () => {
|
|
140
|
+
const { client, socket } = makeHarness({
|
|
141
|
+
maxPendingMessages: 2,
|
|
142
|
+
pendingMessagesOverflowPolicy: "drop_newest",
|
|
143
|
+
});
|
|
144
|
+
client.connectAnonymous();
|
|
145
|
+
const ws = socket();
|
|
146
|
+
ws.triggerOpen();
|
|
147
|
+
client.getMarkets();
|
|
148
|
+
client.getTokens();
|
|
149
|
+
client.getExpiries();
|
|
150
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
151
|
+
const flushedTypes = ws.sent
|
|
152
|
+
.slice(1)
|
|
153
|
+
.map((x) => parseClientMessage(x).type);
|
|
154
|
+
expect(flushedTypes).toEqual(["GetMarkets", "GetTokens"]);
|
|
155
|
+
});
|
|
156
|
+
it("throw policy emits error and does not grow queue", () => {
|
|
157
|
+
const { client, socket } = makeHarness({
|
|
158
|
+
maxPendingMessages: 1,
|
|
159
|
+
pendingMessagesOverflowPolicy: "throw",
|
|
160
|
+
});
|
|
161
|
+
const errors = [];
|
|
162
|
+
client.on("error", (err) => errors.push(err));
|
|
163
|
+
client.connectAnonymous();
|
|
164
|
+
const ws = socket();
|
|
165
|
+
ws.triggerOpen();
|
|
166
|
+
client.getMarkets();
|
|
167
|
+
client.getTokens();
|
|
168
|
+
expect(errors).toHaveLength(1);
|
|
169
|
+
expect(errors[0].message).toContain("Pending queue overflow");
|
|
170
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
171
|
+
const flushedTypes = ws.sent
|
|
172
|
+
.slice(1)
|
|
173
|
+
.map((x) => parseClientMessage(x).type);
|
|
174
|
+
expect(flushedTypes).toEqual(["GetMarkets"]);
|
|
175
|
+
});
|
|
176
|
+
it("applies jitter to reconnect delay", () => {
|
|
177
|
+
jest.useFakeTimers();
|
|
178
|
+
const randomSpy = jest.spyOn(Math, "random").mockReturnValue(1);
|
|
179
|
+
const timeoutSpy = jest.spyOn(global, "setTimeout");
|
|
180
|
+
const { client, socket } = makeHarness({
|
|
181
|
+
autoReconnect: true,
|
|
182
|
+
reconnectDelay: 1000,
|
|
183
|
+
maxReconnectDelay: 30_000,
|
|
184
|
+
reconnectJitterRatio: 0.5,
|
|
185
|
+
});
|
|
186
|
+
client.connectAnonymous();
|
|
187
|
+
const ws = socket();
|
|
188
|
+
ws.triggerOpen();
|
|
189
|
+
ws.triggerClose(1006, "abnormal_close");
|
|
190
|
+
expect(timeoutSpy).toHaveBeenCalled();
|
|
191
|
+
const scheduledDelay = timeoutSpy.mock.calls[0][1];
|
|
192
|
+
expect(scheduledDelay).toBe(1500);
|
|
193
|
+
randomSpy.mockRestore();
|
|
194
|
+
timeoutSpy.mockRestore();
|
|
195
|
+
});
|
|
196
|
+
it("stops auto reconnect on VersionMismatch", () => {
|
|
197
|
+
jest.useFakeTimers();
|
|
198
|
+
const timeoutSpy = jest.spyOn(global, "setTimeout");
|
|
199
|
+
const { client, socket } = makeHarness({
|
|
200
|
+
autoReconnect: true,
|
|
201
|
+
});
|
|
202
|
+
client.connectAnonymous();
|
|
203
|
+
const ws = socket();
|
|
204
|
+
ws.triggerOpen();
|
|
205
|
+
ws.triggerMessage({
|
|
206
|
+
type: "VersionMismatch",
|
|
207
|
+
data: {
|
|
208
|
+
requested_version: "1.0.0",
|
|
209
|
+
server_version: "1.1.0",
|
|
210
|
+
min_supported_version: "1.1.0",
|
|
211
|
+
message: "incompatible protocol version",
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
expect(timeoutSpy).not.toHaveBeenCalled();
|
|
215
|
+
timeoutSpy.mockRestore();
|
|
216
|
+
});
|
|
217
|
+
it("emits orderAccepted event payload", () => {
|
|
218
|
+
const { client } = makeHarness();
|
|
219
|
+
const hex = "11".repeat(32);
|
|
220
|
+
let got = null;
|
|
221
|
+
client.on("orderAccepted", (h) => (got = h));
|
|
222
|
+
client.handleMessage({
|
|
223
|
+
type: "OrderAccepted",
|
|
224
|
+
data: { order_id: hex },
|
|
225
|
+
});
|
|
226
|
+
expect(got).toBe(hex);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MarketDescriptorInfo, TokenInfo, WsU64 } from "./types";
|
|
2
|
+
export type DeriveTokenListsResult = {
|
|
3
|
+
underlyings: TokenInfo[];
|
|
4
|
+
quotesByUnderlying: Record<string, TokenInfo[]>;
|
|
5
|
+
};
|
|
6
|
+
/** Derive token dropdown lists from `MarketDescriptorInfo[]`. */
|
|
7
|
+
export declare function deriveTokenLists(markets: MarketDescriptorInfo[]): DeriveTokenListsResult;
|
|
8
|
+
/** Derive expiry list (unix seconds) from `MarketDescriptorInfo[]`, optionally filtered. */
|
|
9
|
+
export declare function deriveExpiries(markets: MarketDescriptorInfo[], filters?: {
|
|
10
|
+
underlyingMint?: string;
|
|
11
|
+
quoteMint?: string;
|
|
12
|
+
isPut?: boolean | null;
|
|
13
|
+
}): WsU64[];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Derive token dropdown lists from `MarketDescriptorInfo[]`. */
|
|
2
|
+
export function deriveTokenLists(markets) {
|
|
3
|
+
const underlyings = new Map();
|
|
4
|
+
const quotesByUnderlying = new Map();
|
|
5
|
+
for (const m of markets) {
|
|
6
|
+
const uMint = String(m.market.underlying_mint);
|
|
7
|
+
const qMint = String(m.market.quote_mint);
|
|
8
|
+
underlyings.set(uMint, {
|
|
9
|
+
mint: m.market.underlying_mint,
|
|
10
|
+
decimals: m.underlying_decimals,
|
|
11
|
+
symbol: m.underlying_symbol,
|
|
12
|
+
});
|
|
13
|
+
const q = {
|
|
14
|
+
mint: m.market.quote_mint,
|
|
15
|
+
decimals: m.quote_decimals,
|
|
16
|
+
symbol: m.quote_symbol,
|
|
17
|
+
};
|
|
18
|
+
const inner = quotesByUnderlying.get(uMint) ?? new Map();
|
|
19
|
+
inner.set(qMint, q);
|
|
20
|
+
quotesByUnderlying.set(uMint, inner);
|
|
21
|
+
}
|
|
22
|
+
const underlyingsOut = Array.from(underlyings.values()).sort((a, b) => String(a.mint).localeCompare(String(b.mint)));
|
|
23
|
+
const quotesOut = {};
|
|
24
|
+
for (const [u, qs] of quotesByUnderlying.entries()) {
|
|
25
|
+
quotesOut[u] = Array.from(qs.values()).sort((a, b) => String(a.mint).localeCompare(String(b.mint)));
|
|
26
|
+
}
|
|
27
|
+
return { underlyings: underlyingsOut, quotesByUnderlying: quotesOut };
|
|
28
|
+
}
|
|
29
|
+
/** Derive expiry list (unix seconds) from `MarketDescriptorInfo[]`, optionally filtered. */
|
|
30
|
+
export function deriveExpiries(markets, filters) {
|
|
31
|
+
const set = new Set();
|
|
32
|
+
for (const m of markets) {
|
|
33
|
+
if (filters?.underlyingMint &&
|
|
34
|
+
String(m.market.underlying_mint) !== String(filters.underlyingMint))
|
|
35
|
+
continue;
|
|
36
|
+
if (filters?.quoteMint &&
|
|
37
|
+
String(m.market.quote_mint) !== String(filters.quoteMint))
|
|
38
|
+
continue;
|
|
39
|
+
if (filters?.isPut != null && m.market.is_put !== filters.isPut)
|
|
40
|
+
continue;
|
|
41
|
+
set.add(m.market.expiry_ts);
|
|
42
|
+
}
|
|
43
|
+
return Array.from(set.values()).sort((a, b) => a - b);
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS flow helpers that enforce SDK-wide `orderId` rules:
|
|
3
|
+
* - canonical `orderId` is 32 bytes (sha256 preimage)
|
|
4
|
+
* - WS wire uses hex string for `order_id` and base58 for signatures
|
|
5
|
+
*/
|
|
6
|
+
import type { Address } from "@solana/addresses";
|
|
7
|
+
import type { AcceptQuoteMessage, QuoteMessage, RfqBroadcastMessage } from "./types";
|
|
8
|
+
import type { SignerLike } from "../chain/orders";
|
|
9
|
+
export declare function buildSignedQuoteMessage(args: {
|
|
10
|
+
rfqId: string;
|
|
11
|
+
strike: number;
|
|
12
|
+
price: number;
|
|
13
|
+
validUntil: number;
|
|
14
|
+
nonce: number;
|
|
15
|
+
orderId: Uint8Array;
|
|
16
|
+
makerSigner: SignerLike;
|
|
17
|
+
}): Promise<QuoteMessage>;
|
|
18
|
+
/**
|
|
19
|
+
* Strict maker helper: build a fully-valid quote from a server RFQ broadcast.
|
|
20
|
+
*
|
|
21
|
+
* Rules enforced:
|
|
22
|
+
* - `strike` MUST be one of `rfq.order_options`.
|
|
23
|
+
* - `nonce` is maker-provided (server never assigns it). Use your own RNG
|
|
24
|
+
* or the SDK's `NonceGenerator` helper.
|
|
25
|
+
* - `order_id` is computed as sha256(preimage182) using canonical fields.
|
|
26
|
+
* - signature is Ed25519 over 32-byte `order_id`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildSignedQuoteFromRfq(args: {
|
|
29
|
+
rfq: RfqBroadcastMessage;
|
|
30
|
+
strike: number;
|
|
31
|
+
price: number;
|
|
32
|
+
validUntil: number;
|
|
33
|
+
/** Maker-provided u64 nonce (recommended: random). */
|
|
34
|
+
nonce: number;
|
|
35
|
+
/** Maker owner pubkey (base58). Included in order_id preimage. */
|
|
36
|
+
makerOwner: Address<string>;
|
|
37
|
+
/** Maker signing key (ed25519) that signs 32-byte order_id. */
|
|
38
|
+
makerSigner: SignerLike;
|
|
39
|
+
}): Promise<QuoteMessage>;
|
|
40
|
+
export declare function buildAcceptQuoteMessage(args: {
|
|
41
|
+
rfqId: string;
|
|
42
|
+
maker: Address<string>;
|
|
43
|
+
orderId: Uint8Array;
|
|
44
|
+
}): AcceptQuoteMessage;
|
package/dist/ws/flows.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS flow helpers that enforce SDK-wide `orderId` rules:
|
|
3
|
+
* - canonical `orderId` is 32 bytes (sha256 preimage)
|
|
4
|
+
* - WS wire uses hex string for `order_id` and base58 for signatures
|
|
5
|
+
*/
|
|
6
|
+
import { computeOrderId, signOrderIdBase58 } from "../chain/orders";
|
|
7
|
+
import { assertOrderId32, orderIdToHex } from "../types/orderId";
|
|
8
|
+
import { assertWsU64Safe } from "./wirePolicy";
|
|
9
|
+
export async function buildSignedQuoteMessage(args) {
|
|
10
|
+
// WS numeric safety: these fields are u64 in Rust and must be safe integers in JS.
|
|
11
|
+
assertWsU64Safe(args.strike, "strike");
|
|
12
|
+
assertWsU64Safe(args.price, "price");
|
|
13
|
+
assertWsU64Safe(args.validUntil, "validUntil");
|
|
14
|
+
assertWsU64Safe(args.nonce, "nonce");
|
|
15
|
+
assertOrderId32(args.orderId);
|
|
16
|
+
const { signatureBase58 } = await signOrderIdBase58(args.makerSigner, args.orderId);
|
|
17
|
+
return {
|
|
18
|
+
rfq_id: args.rfqId,
|
|
19
|
+
strike: args.strike,
|
|
20
|
+
price: args.price,
|
|
21
|
+
valid_until: args.validUntil,
|
|
22
|
+
nonce: args.nonce,
|
|
23
|
+
order_id: orderIdToHex(args.orderId),
|
|
24
|
+
signature: signatureBase58,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function positionTypeToU8(positionType) {
|
|
28
|
+
switch (positionType) {
|
|
29
|
+
case "covered_call":
|
|
30
|
+
return 0;
|
|
31
|
+
case "cash_secured_put":
|
|
32
|
+
return 1;
|
|
33
|
+
default:
|
|
34
|
+
throw new Error(`Unknown position_type: ${positionType}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Strict maker helper: build a fully-valid quote from a server RFQ broadcast.
|
|
39
|
+
*
|
|
40
|
+
* Rules enforced:
|
|
41
|
+
* - `strike` MUST be one of `rfq.order_options`.
|
|
42
|
+
* - `nonce` is maker-provided (server never assigns it). Use your own RNG
|
|
43
|
+
* or the SDK's `NonceGenerator` helper.
|
|
44
|
+
* - `order_id` is computed as sha256(preimage182) using canonical fields.
|
|
45
|
+
* - signature is Ed25519 over 32-byte `order_id`.
|
|
46
|
+
*/
|
|
47
|
+
export async function buildSignedQuoteFromRfq(args) {
|
|
48
|
+
// WS numeric safety: order_id preimage packs these as u64; JS must not lose precision.
|
|
49
|
+
assertWsU64Safe(args.rfq.market.chain_id, "rfq.market.chain_id");
|
|
50
|
+
assertWsU64Safe(args.rfq.quantity, "rfq.quantity");
|
|
51
|
+
assertWsU64Safe(args.strike, "strike");
|
|
52
|
+
assertWsU64Safe(args.price, "price");
|
|
53
|
+
assertWsU64Safe(args.validUntil, "validUntil");
|
|
54
|
+
assertWsU64Safe(args.nonce, "nonce");
|
|
55
|
+
const opts = args.rfq.order_options;
|
|
56
|
+
if (!opts || opts.length === 0) {
|
|
57
|
+
throw new Error("rfq.order_options is required to build a strict quote");
|
|
58
|
+
}
|
|
59
|
+
const option = opts.find((o) => o.strike === args.strike);
|
|
60
|
+
if (!option) {
|
|
61
|
+
throw new Error("strike must match one of rfq.order_options");
|
|
62
|
+
}
|
|
63
|
+
const orderId = computeOrderId({
|
|
64
|
+
chainId: args.rfq.market.chain_id,
|
|
65
|
+
programId: args.rfq.market.program_id,
|
|
66
|
+
isTakerBuy: false,
|
|
67
|
+
positionType: positionTypeToU8(args.rfq.position_type),
|
|
68
|
+
market: args.rfq.market.market_pda,
|
|
69
|
+
strike: args.strike,
|
|
70
|
+
quantity: args.rfq.quantity,
|
|
71
|
+
price: args.price,
|
|
72
|
+
validUntil: args.validUntil,
|
|
73
|
+
maker: args.makerOwner,
|
|
74
|
+
taker: args.rfq.taker,
|
|
75
|
+
nonce: args.nonce,
|
|
76
|
+
});
|
|
77
|
+
assertOrderId32(orderId);
|
|
78
|
+
const { signatureBase58 } = await signOrderIdBase58(args.makerSigner, orderId);
|
|
79
|
+
return {
|
|
80
|
+
rfq_id: args.rfq.rfq_id,
|
|
81
|
+
strike: args.strike,
|
|
82
|
+
price: args.price,
|
|
83
|
+
valid_until: args.validUntil,
|
|
84
|
+
nonce: args.nonce,
|
|
85
|
+
order_id: orderIdToHex(orderId),
|
|
86
|
+
signature: signatureBase58,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function buildAcceptQuoteMessage(args) {
|
|
90
|
+
assertOrderId32(args.orderId);
|
|
91
|
+
return {
|
|
92
|
+
rfq_id: args.rfqId,
|
|
93
|
+
maker: args.maker,
|
|
94
|
+
order_id: orderIdToHex(args.orderId),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { buildSignedQuoteFromRfq, buildSignedQuoteMessage, } from "./flows";
|
|
2
|
+
import { orderIdToHex } from "../types/orderId";
|
|
3
|
+
import { getAddressDecoder } from "@solana/addresses";
|
|
4
|
+
import { getBase58Decoder } from "@solana/codecs-strings";
|
|
5
|
+
describe("ws flows", () => {
|
|
6
|
+
it("buildSignedQuoteMessage encodes order_id as hex and signature as base58", async () => {
|
|
7
|
+
const base58 = getBase58Decoder();
|
|
8
|
+
const decoder = getAddressDecoder();
|
|
9
|
+
const makerPk = decoder.decode(new Uint8Array(32).fill(7));
|
|
10
|
+
const orderId = new Uint8Array(32).fill(1);
|
|
11
|
+
const mockSig = new Uint8Array(64).fill(9);
|
|
12
|
+
const makerSigner = {
|
|
13
|
+
publicKey: makerPk,
|
|
14
|
+
signMessage: async (msg) => {
|
|
15
|
+
expect(msg).toEqual(orderId);
|
|
16
|
+
return mockSig;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const quote = await buildSignedQuoteMessage({
|
|
20
|
+
rfqId: "rfq-1",
|
|
21
|
+
strike: 1100,
|
|
22
|
+
price: 123,
|
|
23
|
+
validUntil: 456,
|
|
24
|
+
nonce: 42,
|
|
25
|
+
orderId,
|
|
26
|
+
makerSigner,
|
|
27
|
+
});
|
|
28
|
+
expect(quote.strike).toBe(1100);
|
|
29
|
+
expect(quote.order_id).toBe(orderIdToHex(orderId));
|
|
30
|
+
expect(quote.signature).toBe(base58.decode(mockSig));
|
|
31
|
+
});
|
|
32
|
+
it("buildSignedQuoteFromRfq uses maker-provided nonce and computes order_id", async () => {
|
|
33
|
+
const base58 = getBase58Decoder();
|
|
34
|
+
const decoder = getAddressDecoder();
|
|
35
|
+
const makerSigningPk = decoder.decode(new Uint8Array(32).fill(7));
|
|
36
|
+
const mockSig = new Uint8Array(64).fill(9);
|
|
37
|
+
const makerSigner = {
|
|
38
|
+
publicKey: makerSigningPk,
|
|
39
|
+
signMessage: async (msg) => {
|
|
40
|
+
// ensure we are signing a 32-byte order id
|
|
41
|
+
expect(msg).toHaveLength(32);
|
|
42
|
+
return mockSig;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
const rfq = {
|
|
46
|
+
rfq_id: "rfq-1",
|
|
47
|
+
market: {
|
|
48
|
+
chain_id: 0,
|
|
49
|
+
program_id: decoder.decode(new Uint8Array(32).fill(4)),
|
|
50
|
+
market_pda: decoder.decode(new Uint8Array(32).fill(1)),
|
|
51
|
+
underlying_mint: decoder.decode(new Uint8Array(32).fill(11)),
|
|
52
|
+
quote_mint: decoder.decode(new Uint8Array(32).fill(12)),
|
|
53
|
+
expiry_ts: 1_700_000_000,
|
|
54
|
+
is_put: false,
|
|
55
|
+
collateral_mint: decoder.decode(new Uint8Array(32).fill(13)),
|
|
56
|
+
settlement_mint: decoder.decode(new Uint8Array(32).fill(14)),
|
|
57
|
+
},
|
|
58
|
+
position_type: "covered_call",
|
|
59
|
+
strike: 1000,
|
|
60
|
+
quantity: 2,
|
|
61
|
+
expires_at: 999,
|
|
62
|
+
taker: decoder.decode(new Uint8Array(32).fill(2)),
|
|
63
|
+
order_options: [
|
|
64
|
+
{ strike: 1050 },
|
|
65
|
+
{ strike: 1100 },
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
const quote = await buildSignedQuoteFromRfq({
|
|
69
|
+
rfq,
|
|
70
|
+
strike: 1100,
|
|
71
|
+
price: 123,
|
|
72
|
+
validUntil: 456,
|
|
73
|
+
nonce: 43,
|
|
74
|
+
makerOwner: decoder.decode(new Uint8Array(32).fill(3)),
|
|
75
|
+
makerSigner,
|
|
76
|
+
});
|
|
77
|
+
expect(quote.rfq_id).toBe("rfq-1");
|
|
78
|
+
expect(quote.strike).toBe(1100);
|
|
79
|
+
expect(quote.nonce).toBe(43);
|
|
80
|
+
expect(quote.order_id).toHaveLength(64); // hex32
|
|
81
|
+
expect(quote.signature).toBe(base58.decode(mockSig));
|
|
82
|
+
});
|
|
83
|
+
});
|
package/dist/ws/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sponsored tx helpers for WS flow.
|
|
3
|
+
*
|
|
4
|
+
* The server sends `tx_base64` as base64-encoded Solana wire transaction bytes:
|
|
5
|
+
* <shortvec num_sigs> | <sigs[64]*num_sigs> | <message bytes>
|
|
6
|
+
*
|
|
7
|
+
* The taker signs the **message bytes** and fills its signature slot (index 1 by convention:
|
|
8
|
+
* 0 = relayer fee payer, 1 = taker).
|
|
9
|
+
*/
|
|
10
|
+
import type { KeyPairSigner } from "@solana/kit";
|
|
11
|
+
export type MessageSigner = {
|
|
12
|
+
/** Return raw 64-byte ed25519 signature over `message`. */
|
|
13
|
+
signMessage(message: Uint8Array): Promise<Uint8Array>;
|
|
14
|
+
};
|
|
15
|
+
export type WalletStandardMessageSigner = {
|
|
16
|
+
/** Wallet-standard `signMessages` (used by @solana/kit signers and some wallets). */
|
|
17
|
+
signMessages(messages: Array<{
|
|
18
|
+
content: Uint8Array;
|
|
19
|
+
}>): Promise<any>;
|
|
20
|
+
/** Optional address used by wallet-standard result maps. */
|
|
21
|
+
address?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Sign a sponsored tx template in-place and return new base64.
|
|
25
|
+
*
|
|
26
|
+
* Convention:
|
|
27
|
+
* - signature slot 0: relayer (must remain default/zero at this step)
|
|
28
|
+
* - signature slot 1: taker (filled by this helper)
|
|
29
|
+
*/
|
|
30
|
+
export declare function signSponsoredTxBase64(args: {
|
|
31
|
+
txBase64: string;
|
|
32
|
+
/**
|
|
33
|
+
* Any signer capable of producing an ed25519 signature over the transaction message bytes:
|
|
34
|
+
* - @solana/kit KeyPairSigner
|
|
35
|
+
* - wallet-adapter style: { signMessage(bytes): Uint8Array }
|
|
36
|
+
*/
|
|
37
|
+
taker: KeyPairSigner | MessageSigner | WalletStandardMessageSigner;
|
|
38
|
+
takerSignatureIndex?: number;
|
|
39
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sponsored tx helpers for WS flow.
|
|
3
|
+
*
|
|
4
|
+
* The server sends `tx_base64` as base64-encoded Solana wire transaction bytes:
|
|
5
|
+
* <shortvec num_sigs> | <sigs[64]*num_sigs> | <message bytes>
|
|
6
|
+
*
|
|
7
|
+
* The taker signs the **message bytes** and fills its signature slot (index 1 by convention:
|
|
8
|
+
* 0 = relayer fee payer, 1 = taker).
|
|
9
|
+
*/
|
|
10
|
+
async function signMessageWithSigner(signer, message) {
|
|
11
|
+
// Path A: plain signMessage (wallet-adapter style, Privy/Phantom via adapters).
|
|
12
|
+
if (typeof signer.signMessage === "function") {
|
|
13
|
+
const sig = await signer.signMessage(message);
|
|
14
|
+
return sig;
|
|
15
|
+
}
|
|
16
|
+
// Path B: wallet-standard signMessages (used by @solana/kit KeyPairSigner).
|
|
17
|
+
if (typeof signer.signMessages === "function") {
|
|
18
|
+
const res = await signer.signMessages([{ content: message }]);
|
|
19
|
+
const addr = signer.address;
|
|
20
|
+
const sig = addr && res?.[0] ? res?.[0]?.[addr] : Object.values(res?.[0] ?? {})?.[0];
|
|
21
|
+
if (sig instanceof Uint8Array)
|
|
22
|
+
return sig;
|
|
23
|
+
}
|
|
24
|
+
throw new Error("Signer does not support signMessage/signMessages");
|
|
25
|
+
}
|
|
26
|
+
function decodeShortU16Len(bytes, offset = 0) {
|
|
27
|
+
// Solana shortvec: little-endian base128 varint (u16 in practice).
|
|
28
|
+
let value = 0;
|
|
29
|
+
let shift = 0;
|
|
30
|
+
let i = offset;
|
|
31
|
+
while (i < bytes.length) {
|
|
32
|
+
const b = bytes[i];
|
|
33
|
+
value |= (b & 0x7f) << shift;
|
|
34
|
+
i += 1;
|
|
35
|
+
if ((b & 0x80) === 0)
|
|
36
|
+
break;
|
|
37
|
+
shift += 7;
|
|
38
|
+
if (shift > 21)
|
|
39
|
+
throw new Error("shortvec too long");
|
|
40
|
+
}
|
|
41
|
+
return { value, size: i - offset };
|
|
42
|
+
}
|
|
43
|
+
function bytesToBase64(bytes) {
|
|
44
|
+
// Browser
|
|
45
|
+
if (typeof globalThis.btoa === "function") {
|
|
46
|
+
let s = "";
|
|
47
|
+
for (let i = 0; i < bytes.length; i++)
|
|
48
|
+
s += String.fromCharCode(bytes[i]);
|
|
49
|
+
return globalThis.btoa(s);
|
|
50
|
+
}
|
|
51
|
+
// Node
|
|
52
|
+
return Buffer.from(bytes).toString("base64");
|
|
53
|
+
}
|
|
54
|
+
function base64ToBytes(b64) {
|
|
55
|
+
// Browser
|
|
56
|
+
if (typeof globalThis.atob === "function") {
|
|
57
|
+
const bin = globalThis.atob(b64.trim());
|
|
58
|
+
const out = new Uint8Array(bin.length);
|
|
59
|
+
for (let i = 0; i < bin.length; i++)
|
|
60
|
+
out[i] = bin.charCodeAt(i);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
// Node
|
|
64
|
+
return new Uint8Array(Buffer.from(b64.trim(), "base64"));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Sign a sponsored tx template in-place and return new base64.
|
|
68
|
+
*
|
|
69
|
+
* Convention:
|
|
70
|
+
* - signature slot 0: relayer (must remain default/zero at this step)
|
|
71
|
+
* - signature slot 1: taker (filled by this helper)
|
|
72
|
+
*/
|
|
73
|
+
export async function signSponsoredTxBase64(args) {
|
|
74
|
+
const idx = args.takerSignatureIndex ?? 1;
|
|
75
|
+
const bytes = base64ToBytes(args.txBase64);
|
|
76
|
+
const { value: sigCount, size: sigCountSize } = decodeShortU16Len(bytes, 0);
|
|
77
|
+
if (sigCount <= idx) {
|
|
78
|
+
throw new Error(`tx has sigCount=${sigCount}, cannot fill index=${idx}`);
|
|
79
|
+
}
|
|
80
|
+
const sigsStart = sigCountSize;
|
|
81
|
+
const msgStart = sigsStart + sigCount * 64;
|
|
82
|
+
if (msgStart > bytes.length)
|
|
83
|
+
throw new Error("invalid tx bytes (truncated)");
|
|
84
|
+
const msgBytes = bytes.slice(msgStart);
|
|
85
|
+
const sig = await signMessageWithSigner(args.taker, msgBytes);
|
|
86
|
+
if (!(sig instanceof Uint8Array) || sig.length !== 64) {
|
|
87
|
+
throw new Error("signer returned invalid signature");
|
|
88
|
+
}
|
|
89
|
+
const out = Uint8Array.from(bytes);
|
|
90
|
+
out.set(sig, sigsStart + idx * 64);
|
|
91
|
+
return bytesToBase64(out);
|
|
92
|
+
}
|