@rhinestone/sdk 0.0.0-dev-20260917215313 → 0.0.0-dev-20260921130703

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.
Files changed (68) hide show
  1. package/README.md +2 -20
  2. package/dist/src/actions/runtime.d.ts.map +1 -1
  3. package/dist/src/api/account.d.ts +62 -91
  4. package/dist/src/api/account.d.ts.map +1 -1
  5. package/dist/src/api/account.js +28 -697
  6. package/dist/src/api/accounts.d.ts +1 -1
  7. package/dist/src/api/accounts.d.ts.map +1 -1
  8. package/dist/src/api/accounts.js +3 -121
  9. package/dist/src/api/compose-types.d.ts +0 -9
  10. package/dist/src/api/compose-types.d.ts.map +1 -1
  11. package/dist/src/api/compose.d.ts.map +1 -1
  12. package/dist/src/api/compose.js +5 -50
  13. package/dist/src/api/sdk.d.ts +2 -2
  14. package/dist/src/api/sdk.d.ts.map +1 -1
  15. package/dist/src/api/sdk.js +1 -1
  16. package/dist/src/chains/caip2.d.ts.map +1 -1
  17. package/dist/src/chains/caip2.js +0 -5
  18. package/dist/src/chains/non-evm.d.ts +12 -132
  19. package/dist/src/chains/non-evm.d.ts.map +1 -1
  20. package/dist/src/chains/non-evm.js +1 -30
  21. package/dist/src/clients/orchestrator/client.js +1 -1
  22. package/dist/src/clients/orchestrator/errors.d.ts +2 -22
  23. package/dist/src/clients/orchestrator/errors.d.ts.map +1 -1
  24. package/dist/src/clients/orchestrator/errors.js +6 -61
  25. package/dist/src/clients/orchestrator/mappers.d.ts +0 -2
  26. package/dist/src/clients/orchestrator/mappers.d.ts.map +1 -1
  27. package/dist/src/clients/orchestrator/mappers.js +2 -87
  28. package/dist/src/clients/orchestrator/public.d.ts +7 -49
  29. package/dist/src/clients/orchestrator/public.d.ts.map +1 -1
  30. package/dist/src/clients/orchestrator/types.d.ts +4 -17
  31. package/dist/src/clients/orchestrator/types.d.ts.map +1 -1
  32. package/dist/src/clients/orchestrator/wire.d.ts +19 -1
  33. package/dist/src/clients/orchestrator/wire.d.ts.map +1 -1
  34. package/dist/src/config/account.d.ts +11 -185
  35. package/dist/src/config/account.d.ts.map +1 -1
  36. package/dist/src/errors/execution.d.ts +1 -11
  37. package/dist/src/errors/execution.d.ts.map +1 -1
  38. package/dist/src/errors/execution.js +1 -27
  39. package/dist/src/errors/index.d.ts +3 -4
  40. package/dist/src/errors/index.d.ts.map +1 -1
  41. package/dist/src/errors/index.js +4 -7
  42. package/dist/src/hypercore/market.d.ts +1 -1
  43. package/dist/src/hypercore/market.js +1 -1
  44. package/dist/src/index.d.ts +7 -7
  45. package/dist/src/index.d.ts.map +1 -1
  46. package/dist/src/index.js +2 -2
  47. package/dist/src/transactions/intents/normalize.d.ts.map +1 -1
  48. package/dist/src/transactions/intents/normalize.js +2 -9
  49. package/dist/src/transactions/intents/prepare.d.ts.map +1 -1
  50. package/dist/src/transactions/intents/prepare.js +1 -10
  51. package/dist/src/transactions/intents/types.d.ts +3 -61
  52. package/dist/src/transactions/intents/types.d.ts.map +1 -1
  53. package/dist/src/utils/index.d.ts +9 -9
  54. package/dist/src/utils/index.d.ts.map +1 -1
  55. package/dist/src/utils/index.js +6 -6
  56. package/package.json +1 -5
  57. package/dist/src/accounts/solana/address.d.ts +0 -28
  58. package/dist/src/accounts/solana/address.d.ts.map +0 -1
  59. package/dist/src/accounts/solana/address.js +0 -105
  60. package/dist/src/errors/capability.d.ts +0 -23
  61. package/dist/src/errors/capability.d.ts.map +0 -1
  62. package/dist/src/errors/capability.js +0 -41
  63. package/dist/src/transactions/intents/solana-instructions.d.ts +0 -14
  64. package/dist/src/transactions/intents/solana-instructions.d.ts.map +0 -1
  65. package/dist/src/transactions/intents/solana-instructions.js +0 -114
  66. package/dist/src/transactions/intents/solana.d.ts +0 -88
  67. package/dist/src/transactions/intents/solana.d.ts.map +0 -1
  68. package/dist/src/transactions/intents/solana.js +0 -302
@@ -1,88 +0,0 @@
1
- import { type Account, type Address, type Hex } from 'viem';
2
- import type { SolanaAddress, SolanaChain, SolanaInstruction } from '../../chains/non-evm.js';
3
- import type { IntentQuotePort, IntentSubmissionPort } from '../../clients/orchestrator/port.js';
4
- import type { AppFeeRate, PersonalSignOriginSignData, ProtocolFeeRate, SerializedIntentInput } from '../../clients/orchestrator/public.js';
5
- import type { OrchestratorIntentRequest, OrchestratorQuote } from '../../clients/orchestrator/types.js';
6
- /**
7
- * Where a Solana-origin spend lands. The two arms are exclusive on purpose:
8
- * every validation site has to state which direction it is checking, so a
9
- * same-chain rule cannot silently apply to a cross-chain quote.
10
- */
11
- export type SolanaDelivery = {
12
- readonly kind: 'same-chain';
13
- readonly recipient: SolanaAddress;
14
- } | {
15
- readonly kind: 'cross-chain';
16
- readonly chainId: number;
17
- readonly token: Address;
18
- readonly recipient: Address;
19
- };
20
- /**
21
- * What a Solana-origin intent does: move one SPL mint, or run caller-supplied
22
- * instructions out of the account's own wallet. The arms are exclusive — an
23
- * instruction execution is tokenless and names no recipient.
24
- */
25
- export type SolanaAction = {
26
- readonly kind: 'transfer';
27
- readonly mint: SolanaAddress;
28
- readonly amount?: bigint;
29
- readonly delivery: SolanaDelivery;
30
- } | {
31
- readonly kind: 'instructions';
32
- readonly instructions: readonly SolanaInstruction[];
33
- readonly addressLookupTables?: readonly string[];
34
- };
35
- export interface SolanaTransferInput {
36
- readonly chain: SolanaChain;
37
- readonly action: SolanaAction;
38
- readonly accountAddress: Address;
39
- readonly accountType: 'GENERIC' | 'ERC7579' | 'EOA';
40
- readonly authority: Address;
41
- readonly walletAddress: SolanaAddress;
42
- readonly swigAddress: SolanaAddress;
43
- readonly namespace: 'dev-v1';
44
- readonly endpoint: string;
45
- readonly appFees?: AppFeeRate;
46
- readonly protocolFees?: ProtocolFeeRate;
47
- }
48
- export interface PreparedSolanaIntent {
49
- readonly traceId: string;
50
- readonly input: SolanaTransferInput;
51
- readonly request: OrchestratorIntentRequest;
52
- readonly quote: OrchestratorQuote;
53
- readonly quotes: readonly OrchestratorQuote[];
54
- }
55
- export interface SignedSolanaIntent {
56
- readonly prepared: PreparedSolanaIntent;
57
- readonly signature: Hex;
58
- }
59
- export interface SolanaWorkflowContext {
60
- readonly quoteClient: IntentQuotePort;
61
- readonly submissionClient: IntentSubmissionPort;
62
- readonly now: () => number;
63
- }
64
- export declare function solanaChainId(chain: SolanaChain): number;
65
- export declare function buildSolanaIntentRequest(input: SolanaTransferInput): OrchestratorIntentRequest;
66
- export declare function prepareSolanaIntent(context: SolanaWorkflowContext, input: SolanaTransferInput): Promise<PreparedSolanaIntent>;
67
- export declare function reconstructSolanaIntent(input: {
68
- readonly traceId: string;
69
- readonly transfer: SolanaTransferInput;
70
- readonly intentInput: SerializedIntentInput;
71
- readonly quote: OrchestratorQuote;
72
- readonly quotes: readonly OrchestratorQuote[];
73
- }): PreparedSolanaIntent;
74
- export declare function assertSolanaNotExpired(now: number, quote: OrchestratorQuote): void;
75
- export declare function signSolanaIntent(input: {
76
- readonly prepared: PreparedSolanaIntent;
77
- readonly owner: Account;
78
- readonly now: () => number;
79
- }): Promise<SignedSolanaIntent>;
80
- export declare function validateSolanaSignature(authority: Address, payload: PersonalSignOriginSignData, signature: Hex): Promise<void>;
81
- export declare function submitSolanaIntent(context: SolanaWorkflowContext, signed: SignedSolanaIntent): Promise<{
82
- type: "intent";
83
- traceId: string;
84
- intentId: string;
85
- sourceChains: number[];
86
- targetChain: number;
87
- }>;
88
- //# sourceMappingURL=solana.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"solana.d.ts","sourceRoot":"","sources":["../../../../transactions/intents/solana.ts"],"names":[],"mappings":"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"}
@@ -1,302 +0,0 @@
1
- import { hexToBytes, isAddress, isAddressEqual, recoverMessageAddress, } from 'viem';
2
- import { formatCaip2 } from '../../chains/caip2.js';
3
- import { solanaAddress, solanaDevnet, solanaMainnet, } from '../../chains/non-evm.js';
4
- import { InvalidSolanaTransactionArtifactError, SolanaQuoteExpiredError, } from '../../errors/execution.js';
5
- import { normalizeRecovery } from '../../signing/signers/ecdsa.js';
6
- import { projectCompatibleIntentInput } from './compatibility.js';
7
- import { normalizeIntentQuote } from './normalize.js';
8
- import { normalizeSolanaAddressLookupTables, normalizeSolanaInstructions, } from './solana-instructions.js';
9
- const SOLANA_MAINNET_ID = 792703809;
10
- const SOLANA_DEVNET_ID = 792703810;
11
- const SOLANA_MAINNET_CAIP2 = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
12
- const SOLANA_DEVNET_CAIP2 = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1';
13
- const NATIVE_SOL_SENTINEL = '11111111111111111111111111111111';
14
- export function solanaChainId(chain) {
15
- const expected = chain.caip2 === SOLANA_MAINNET_CAIP2
16
- ? solanaMainnet
17
- : chain.caip2 === SOLANA_DEVNET_CAIP2
18
- ? solanaDevnet
19
- : undefined;
20
- if (!expected ||
21
- chain.kind !== expected.kind ||
22
- chain.name !== expected.name ||
23
- chain.testnet !== expected.testnet ||
24
- chain.nativeCurrency.name !== expected.nativeCurrency.name ||
25
- chain.nativeCurrency.symbol !== expected.nativeCurrency.symbol ||
26
- chain.nativeCurrency.decimals !== expected.nativeCurrency.decimals) {
27
- throw new InvalidSolanaTransactionArtifactError('the chain must be the canonical Solana mainnet or devnet descriptor');
28
- }
29
- return chain.caip2 === SOLANA_MAINNET_CAIP2
30
- ? SOLANA_MAINNET_ID
31
- : SOLANA_DEVNET_ID;
32
- }
33
- function validateFee(name, fee) {
34
- if (!fee)
35
- return;
36
- if (typeof fee !== 'object' ||
37
- typeof fee.feeBps !== 'number' ||
38
- !Number.isInteger(fee.feeBps) ||
39
- fee.feeBps < 0 ||
40
- fee.feeBps > 10_000) {
41
- throw new InvalidSolanaTransactionArtifactError(`${name}.feeBps must be an integer from 0 to 10000`);
42
- }
43
- }
44
- export function buildSolanaIntentRequest(input) {
45
- const chainId = solanaChainId(input.chain);
46
- if (input.namespace !== 'dev-v1') {
47
- throw new InvalidSolanaTransactionArtifactError('the managed account namespace must be dev-v1');
48
- }
49
- validateFee('appFees', input.appFees);
50
- validateFee('protocolFees', input.protocolFees);
51
- const account = {
52
- address: input.accountAddress,
53
- accountType: input.accountType,
54
- };
55
- const options = {
56
- signatureMode: 1,
57
- ...(input.appFees ? { appFees: input.appFees } : {}),
58
- ...(input.protocolFees ? { protocolFees: input.protocolFees } : {}),
59
- };
60
- if (input.action.kind === 'instructions') {
61
- if (input.appFees || input.protocolFees) {
62
- throw new InvalidSolanaTransactionArtifactError('a Solana instruction execution carries no value leg to charge fees on');
63
- }
64
- const lookupTables = normalizeSolanaAddressLookupTables(input.action.addressLookupTables);
65
- return {
66
- destinationExecutions: [],
67
- account,
68
- options,
69
- destinationChainId: chainId,
70
- // Tokenless: the instructions move whatever they move, and the payee is
71
- // encoded inside them, so the request names neither token nor recipient.
72
- tokenRequests: [],
73
- destinationInstructions: normalizeSolanaInstructions(input.action.instructions),
74
- ...(lookupTables ? { addressLookupTableAddresses: lookupTables } : {}),
75
- accountAccessList: { chainIds: [chainId] },
76
- };
77
- }
78
- const mint = solanaAddress(input.action.mint);
79
- if (mint === NATIVE_SOL_SENTINEL) {
80
- throw new InvalidSolanaTransactionArtifactError('native SOL transfers are not supported; provide an SPL mint');
81
- }
82
- if (input.action.amount !== undefined &&
83
- (typeof input.action.amount !== 'bigint' || input.action.amount <= 0n)) {
84
- throw new InvalidSolanaTransactionArtifactError('the token amount must be a positive bigint when provided');
85
- }
86
- const amount = input.action.amount === undefined
87
- ? {}
88
- : { amount: input.action.amount };
89
- const common = {
90
- destinationExecutions: [],
91
- account,
92
- options,
93
- };
94
- const delivery = input.action.delivery;
95
- if (delivery.kind === 'cross-chain') {
96
- if (!Number.isSafeInteger(delivery.chainId) ||
97
- delivery.chainId < 0 ||
98
- !formatCaip2(delivery.chainId).startsWith('eip155:')) {
99
- throw new InvalidSolanaTransactionArtifactError('the delivery chain must be an EVM chain');
100
- }
101
- if (!isAddress(delivery.token) || !isAddress(delivery.recipient)) {
102
- throw new InvalidSolanaTransactionArtifactError('the delivery token and recipient must be EVM addresses');
103
- }
104
- return {
105
- ...common,
106
- destinationChainId: delivery.chainId,
107
- tokenRequests: [{ tokenAddress: delivery.token, ...amount }],
108
- recipient: { address: delivery.recipient },
109
- // `chainIds` and `chainTokens` are unioned by the orchestrator, so naming
110
- // the cluster as well would re-expand the source scope to every registry
111
- // token on it and defeat the explicit source mint.
112
- accountAccessList: { chainTokens: { [chainId]: [mint] } },
113
- };
114
- }
115
- const recipient = solanaAddress(delivery.recipient);
116
- if (recipient === input.walletAddress) {
117
- throw new InvalidSolanaTransactionArtifactError('the recipient must differ from the managed Solana wallet');
118
- }
119
- return {
120
- ...common,
121
- destinationChainId: chainId,
122
- tokenRequests: [{ tokenAddress: mint, ...amount }],
123
- recipient: { address: recipient },
124
- accountAccessList: { chainIds: [chainId] },
125
- };
126
- }
127
- function deliveryKind(input) {
128
- return input.action.kind === 'instructions'
129
- ? 'same-chain'
130
- : input.action.delivery.kind;
131
- }
132
- function personalPayload(quote, delivery) {
133
- const origin = quote.signData.origin[0];
134
- // Not a `=== 'RELAY'` whitelist: the same corridor is planned on other
135
- // settlement layers, and any of them authorizes the spend identically.
136
- const layerMismatch = delivery === 'same-chain'
137
- ? quote.settlementLayer !== 'SAME_CHAIN'
138
- : quote.settlementLayer === 'SAME_CHAIN';
139
- if (!quote.intentId ||
140
- layerMismatch ||
141
- quote.signData.origin.length !== 1 ||
142
- origin?.kind !== 'personalSign' ||
143
- !/^[0-9a-fA-F]{64}$/u.test(origin.message) ||
144
- !/^\d+$/u.test(origin.expiresAtSlot) ||
145
- quote.signData.destination !== undefined ||
146
- quote.signData.targetExecution !== undefined) {
147
- throw new InvalidSolanaTransactionArtifactError(delivery === 'same-chain'
148
- ? 'the quote must be SAME_CHAIN with exactly one personal-sign origin and no destination or target signature'
149
- : 'the quote must be a cross-chain route with exactly one personal-sign origin and no destination or target signature', { intentId: quote.intentId });
150
- }
151
- return origin;
152
- }
153
- function validateQuote(quote, input) {
154
- personalPayload(quote, deliveryKind(input));
155
- const chainId = solanaChainId(input.chain);
156
- if (input.action.kind === 'instructions') {
157
- // The serving route decides how many cost legs an instruction execution
158
- // has, so only their chain is asserted: anything off the requested cluster
159
- // is not this intent.
160
- const offChain = [...quote.cost.input, ...quote.cost.output].some((leg) => leg.chainId !== chainId);
161
- if (offChain) {
162
- throw new InvalidSolanaTransactionArtifactError('every quote cost must reference the requested Solana chain', { intentId: quote.intentId });
163
- }
164
- return;
165
- }
166
- if (quote.cost.input.length !== 1 || quote.cost.output.length !== 1) {
167
- throw new InvalidSolanaTransactionArtifactError('quote costs must include exactly one input and one output entry', { intentId: quote.intentId });
168
- }
169
- const source = quote.cost.input[0];
170
- if (source.chainId !== chainId || source.tokenAddress !== input.action.mint) {
171
- throw new InvalidSolanaTransactionArtifactError('the quote input cost must reference the requested Solana chain and mint', { intentId: quote.intentId });
172
- }
173
- const output = quote.cost.output[0];
174
- const delivery = input.action.delivery;
175
- if (delivery.kind === 'same-chain') {
176
- if (output.chainId !== chainId ||
177
- output.tokenAddress !== input.action.mint) {
178
- throw new InvalidSolanaTransactionArtifactError('the quote output cost must reference the requested Solana chain and mint', { intentId: quote.intentId });
179
- }
180
- return;
181
- }
182
- // The orchestrator lowercases EVM addresses; base58 mints stay exact.
183
- if (output.chainId !== delivery.chainId ||
184
- typeof output.tokenAddress !== 'string' ||
185
- output.tokenAddress.toLowerCase() !== delivery.token.toLowerCase()) {
186
- throw new InvalidSolanaTransactionArtifactError('the quote output cost must reference the requested delivery chain and token', { intentId: quote.intentId });
187
- }
188
- }
189
- export async function prepareSolanaIntent(context, input) {
190
- const request = buildSolanaIntentRequest(input);
191
- const response = await context.quoteClient.createQuote(request);
192
- if (response.routes.length === 0) {
193
- throw new InvalidSolanaTransactionArtifactError('the orchestrator returned no quote');
194
- }
195
- const quotes = response.routes.map(normalizeIntentQuote);
196
- for (const quote of quotes)
197
- validateQuote(quote, input);
198
- return {
199
- traceId: response.traceId,
200
- input,
201
- request,
202
- quote: quotes[0],
203
- quotes,
204
- };
205
- }
206
- function canonical(value) {
207
- if (typeof value === 'bigint')
208
- return { $bigint: value.toString() };
209
- if (Array.isArray(value))
210
- return value.map(canonical);
211
- if (value && typeof value === 'object') {
212
- return Object.fromEntries(Object.entries(value)
213
- .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
214
- .map(([key, item]) => [key, canonical(item)]));
215
- }
216
- return value;
217
- }
218
- function stable(value) {
219
- return JSON.stringify(canonical(value));
220
- }
221
- export function reconstructSolanaIntent(input) {
222
- const request = buildSolanaIntentRequest(input.transfer);
223
- if (stable(projectCompatibleIntentInput(request)) !== stable(input.intentInput)) {
224
- throw new InvalidSolanaTransactionArtifactError('the canonical intent input does not match the captured transaction', { intentId: input.quote.intentId });
225
- }
226
- const quotes = input.quotes.map(normalizeIntentQuote);
227
- const quote = quotes.find(({ intentId }) => intentId === input.quote.intentId);
228
- if (!quote || stable(quote) !== stable(normalizeIntentQuote(input.quote))) {
229
- throw new InvalidSolanaTransactionArtifactError('the selected quote is missing or differs from the prepared quote set', { intentId: input.quote.intentId });
230
- }
231
- for (const candidate of quotes)
232
- validateQuote(candidate, input.transfer);
233
- return {
234
- traceId: input.traceId,
235
- input: input.transfer,
236
- request,
237
- quote,
238
- quotes,
239
- };
240
- }
241
- export function assertSolanaNotExpired(now, quote) {
242
- if (!Number.isFinite(quote.expiresAt) || now >= quote.expiresAt * 1000) {
243
- throw new SolanaQuoteExpiredError(quote.intentId);
244
- }
245
- }
246
- export async function signSolanaIntent(input) {
247
- assertSolanaNotExpired(input.now(), input.prepared.quote);
248
- const payload = personalPayload(input.prepared.quote, deliveryKind(input.prepared.input));
249
- if (!input.owner.signMessage) {
250
- throw new InvalidSolanaTransactionArtifactError('the configured authority cannot sign messages; provide a viem account with signMessage for headless signing', { intentId: input.prepared.quote.intentId });
251
- }
252
- const signature = normalizeRecovery(await input.owner.signMessage({ message: payload.message }));
253
- await validateSolanaSignature(input.prepared.input.authority, payload, signature);
254
- return { prepared: input.prepared, signature };
255
- }
256
- export async function validateSolanaSignature(authority, payload, signature) {
257
- let signatureLength;
258
- try {
259
- signatureLength = hexToBytes(signature).length;
260
- }
261
- catch {
262
- signatureLength = -1;
263
- }
264
- if (signatureLength !== 65) {
265
- throw new InvalidSolanaTransactionArtifactError('the origin signature must be a recoverable 65-byte ECDSA signature');
266
- }
267
- let recovered;
268
- try {
269
- recovered = await recoverMessageAddress({
270
- message: payload.message,
271
- signature,
272
- });
273
- }
274
- catch {
275
- throw new InvalidSolanaTransactionArtifactError('the origin signature is not recoverable');
276
- }
277
- if (!isAddressEqual(recovered, authority)) {
278
- throw new InvalidSolanaTransactionArtifactError('the origin signature does not recover to the configured Solana authority');
279
- }
280
- }
281
- export async function submitSolanaIntent(context, signed) {
282
- assertSolanaNotExpired(context.now(), signed.prepared.quote);
283
- const action = signed.prepared.input.action;
284
- const payload = personalPayload(signed.prepared.quote, deliveryKind(signed.prepared.input));
285
- await validateSolanaSignature(signed.prepared.input.authority, payload, signed.signature);
286
- const response = await context.submissionClient.submitIntent({
287
- intentId: signed.prepared.quote.intentId,
288
- signatures: { origin: [signed.signature] },
289
- }, {
290
- intentInput: projectCompatibleIntentInput(signed.prepared.request),
291
- sponsored: false,
292
- });
293
- return {
294
- type: 'intent',
295
- traceId: response.traceId,
296
- intentId: response.intentId,
297
- sourceChains: [solanaChainId(signed.prepared.input.chain)],
298
- targetChain: action.kind === 'transfer' && action.delivery.kind === 'cross-chain'
299
- ? action.delivery.chainId
300
- : solanaChainId(signed.prepared.input.chain),
301
- };
302
- }