@layerswap/wallet-stellar 2.1.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.
Files changed (47) hide show
  1. package/dist/esm/constants.js +8 -0
  2. package/dist/esm/index.js +50 -0
  3. package/dist/esm/service/StellarConnectionService.js +205 -0
  4. package/dist/esm/service/StellarWalletConnectModule.js +218 -0
  5. package/dist/esm/service/createStellarConnection.js +63 -0
  6. package/dist/esm/service/stellarConnector.js +23 -0
  7. package/dist/esm/service/stellarKitManager.js +272 -0
  8. package/dist/esm/service/stellarStore.js +14 -0
  9. package/dist/esm/stellarBalanceProvider.js +49 -0
  10. package/dist/esm/stellarBalances.js +40 -0
  11. package/dist/esm/stellarGasProvider.js +22 -0
  12. package/dist/esm/stellarNetwork.js +34 -0
  13. package/dist/esm/stellarServers.js +51 -0
  14. package/dist/esm/transferProvider/createStellarTransfer.js +122 -0
  15. package/dist/esm/transferProvider/validateStellarXdr.js +249 -0
  16. package/dist/tsconfig.tsbuildinfo +1 -0
  17. package/dist/types/constants.d.ts +5 -0
  18. package/dist/types/constants.d.ts.map +1 -0
  19. package/dist/types/index.d.ts +14 -0
  20. package/dist/types/index.d.ts.map +1 -0
  21. package/dist/types/service/StellarConnectionService.d.ts +44 -0
  22. package/dist/types/service/StellarConnectionService.d.ts.map +1 -0
  23. package/dist/types/service/StellarWalletConnectModule.d.ts +86 -0
  24. package/dist/types/service/StellarWalletConnectModule.d.ts.map +1 -0
  25. package/dist/types/service/createStellarConnection.d.ts +7 -0
  26. package/dist/types/service/createStellarConnection.d.ts.map +1 -0
  27. package/dist/types/service/stellarConnector.d.ts +4 -0
  28. package/dist/types/service/stellarConnector.d.ts.map +1 -0
  29. package/dist/types/service/stellarKitManager.d.ts +37 -0
  30. package/dist/types/service/stellarKitManager.d.ts.map +1 -0
  31. package/dist/types/service/stellarStore.d.ts +24 -0
  32. package/dist/types/service/stellarStore.d.ts.map +1 -0
  33. package/dist/types/stellarBalanceProvider.d.ts +6 -0
  34. package/dist/types/stellarBalanceProvider.d.ts.map +1 -0
  35. package/dist/types/stellarBalances.d.ts +22 -0
  36. package/dist/types/stellarBalances.d.ts.map +1 -0
  37. package/dist/types/stellarGasProvider.d.ts +11 -0
  38. package/dist/types/stellarGasProvider.d.ts.map +1 -0
  39. package/dist/types/stellarNetwork.d.ts +7 -0
  40. package/dist/types/stellarNetwork.d.ts.map +1 -0
  41. package/dist/types/stellarServers.d.ts +5 -0
  42. package/dist/types/stellarServers.d.ts.map +1 -0
  43. package/dist/types/transferProvider/createStellarTransfer.d.ts +3 -0
  44. package/dist/types/transferProvider/createStellarTransfer.d.ts.map +1 -0
  45. package/dist/types/transferProvider/validateStellarXdr.d.ts +22 -0
  46. package/dist/types/transferProvider/validateStellarXdr.d.ts.map +1 -0
  47. package/package.json +63 -0
@@ -0,0 +1,249 @@
1
+ import { Address, Operation, StrKey, Transaction, TransactionBuilder, nativeToScVal, scValToNative, xdr, } from '@stellar/stellar-sdk';
2
+ import { bytesToHex } from '@layerswap/utils/common';
3
+ import { isValidStellarAddress } from '@layerswap/utils';
4
+ import { ActionMessageType } from '@layerswap/widget-types';
5
+ import { resolveStellarAsset } from '../stellarNetwork';
6
+ function staleError(message) {
7
+ const error = new Error(message);
8
+ error.name = ActionMessageType.TransactionExpired;
9
+ return error;
10
+ }
11
+ function readAddress(value, label) {
12
+ try {
13
+ return Address.fromScVal(value).toString();
14
+ }
15
+ catch {
16
+ throw new Error(`Stellar depository ${label} is not an address`);
17
+ }
18
+ }
19
+ function readBytes(value, label) {
20
+ const decoded = scValToNative(value);
21
+ if (!(decoded instanceof Uint8Array)) {
22
+ throw new Error(`Stellar depository ${label} is not bytes`);
23
+ }
24
+ return decoded;
25
+ }
26
+ function readInteger(value, label) {
27
+ const decoded = scValToNative(value);
28
+ if (typeof decoded !== 'bigint') {
29
+ throw new Error(`Stellar depository ${label} is not an integer`);
30
+ }
31
+ return decoded;
32
+ }
33
+ function assertInvocation(invocation, expectedContract, expectedFunction, expectedArgs, label) {
34
+ if (Address.fromScAddress(invocation.contractAddress).toString() !== expectedContract) {
35
+ throw new Error(`Stellar ${label} contract does not match the deposit action`);
36
+ }
37
+ if (invocation.functionName.toString() !== expectedFunction) {
38
+ throw new Error(`Stellar ${label} function does not match the deposit action`);
39
+ }
40
+ if (invocation.args.length !== expectedArgs.length) {
41
+ throw new Error(`Stellar ${label} arguments do not match the deposit action`);
42
+ }
43
+ for (let index = 0; index < expectedArgs.length; index += 1) {
44
+ if (bytesToHex(invocation.args[index].toXdr()) !== bytesToHex(expectedArgs[index].toXdr())) {
45
+ throw new Error(`Stellar ${label} arguments do not match the deposit action`);
46
+ }
47
+ }
48
+ }
49
+ function encodeDepositId(sequenceNumber) {
50
+ if (!Number.isSafeInteger(sequenceNumber) || sequenceNumber < 0) {
51
+ throw new Error('Stellar swap sequence number is invalid');
52
+ }
53
+ return BigInt(sequenceNumber).toString(16).padStart(64, '0');
54
+ }
55
+ function validateAddresses(selectedAddress, depositoryContract) {
56
+ if (!selectedAddress.startsWith('G') || !isValidStellarAddress(selectedAddress)) {
57
+ throw new Error('Selected Stellar source account is invalid');
58
+ }
59
+ if (!StrKey.isValidContract(depositoryContract)) {
60
+ throw new Error('Stellar depository destination must be a C-address');
61
+ }
62
+ }
63
+ function validateExpectedArguments(params) {
64
+ const { args, selectedAddress, tokenContract, amountInBaseUnits, encodedArgs, swapSequenceNumber } = params;
65
+ if (args.length !== 5)
66
+ throw new Error('Stellar depository deposit must contain exactly five arguments');
67
+ if (!/^[1-9]\d*$/.test(amountInBaseUnits))
68
+ throw new Error('Stellar deposit amount is invalid');
69
+ const source = readAddress(args[0], 'source');
70
+ const depositId = bytesToHex(readBytes(args[1], 'ID'));
71
+ const assetContract = readAddress(args[2], 'asset contract');
72
+ const receiver = readAddress(args[3], 'receiver');
73
+ const amount = readInteger(args[4], 'amount').toString();
74
+ const expectedDepositId = encodeDepositId(swapSequenceNumber);
75
+ if (source !== selectedAddress)
76
+ throw new Error('Stellar depository source does not match the connected account');
77
+ if (depositId !== expectedDepositId)
78
+ throw new Error('Stellar depository ID does not match the swap');
79
+ if (assetContract !== tokenContract)
80
+ throw new Error('Stellar depository asset does not match the quote');
81
+ if (!StrKey.isValidEd25519PublicKey(receiver))
82
+ throw new Error('Stellar depository receiver is invalid');
83
+ if (amount !== amountInBaseUnits)
84
+ throw new Error('Stellar depository amount does not match the deposit action');
85
+ const expectedEncodedArgs = [depositId, assetContract, receiver, amount];
86
+ if (encodedArgs.length !== expectedEncodedArgs.length
87
+ || encodedArgs.some((value, index) => value !== expectedEncodedArgs[index])) {
88
+ throw new Error('Stellar encoded_args do not match the transaction');
89
+ }
90
+ return { receiver };
91
+ }
92
+ function validateDepositOperation(operation, params) {
93
+ const { selectedAddress, depositoryContract, networkPassphrase, token, amountInBaseUnits, encodedArgs, swapSequenceNumber, } = params;
94
+ if (operation.type !== 'invokeHostFunction') {
95
+ throw new Error('Stellar deposit XDR must contain one Soroban contract invocation');
96
+ }
97
+ if (operation.source !== selectedAddress) {
98
+ throw new Error('Stellar depository operation source does not match the connected account');
99
+ }
100
+ if (operation.func.type !== 'hostFunctionTypeInvokeContract') {
101
+ throw new Error('Stellar deposit XDR must invoke the depository contract');
102
+ }
103
+ const invocation = operation.func.invokeContract;
104
+ if (Address.fromScAddress(invocation.contractAddress).toString() !== depositoryContract) {
105
+ throw new Error('Stellar depository contract does not match the deposit action');
106
+ }
107
+ if (invocation.functionName.toString() !== 'deposit') {
108
+ throw new Error('Stellar depository function must be deposit');
109
+ }
110
+ const tokenContract = resolveStellarAsset(token).contractId(networkPassphrase);
111
+ const { receiver } = validateExpectedArguments({
112
+ args: invocation.args,
113
+ selectedAddress,
114
+ tokenContract,
115
+ amountInBaseUnits,
116
+ encodedArgs,
117
+ swapSequenceNumber,
118
+ });
119
+ return { tokenContract, receiver };
120
+ }
121
+ export function validateStellarOperationXdr(params) {
122
+ const { operationXdr, selectedAddress, depositoryContract } = params;
123
+ validateAddresses(selectedAddress, depositoryContract);
124
+ if (!operationXdr)
125
+ throw new Error('Stellar deposit action is missing operation XDR');
126
+ let encodedOperation;
127
+ try {
128
+ encodedOperation = xdr.Operation.fromXdr(operationXdr, 'base64');
129
+ }
130
+ catch (cause) {
131
+ throw new Error('Stellar deposit action contains invalid operation XDR', { cause });
132
+ }
133
+ const operation = Operation.fromXdrObject(encodedOperation);
134
+ validateDepositOperation(operation, params);
135
+ if (operation.type !== 'invokeHostFunction' || (operation.auth?.length ?? 0) !== 0) {
136
+ throw new Error('Unsigned Stellar depository operation must not contain authorization entries');
137
+ }
138
+ return encodedOperation;
139
+ }
140
+ export function buildStellarDepositOperation(params) {
141
+ const { selectedAddress, depositoryContract, encodedArgs } = params;
142
+ validateAddresses(selectedAddress, depositoryContract);
143
+ if (encodedArgs.length !== 4)
144
+ throw new Error('Stellar deposit must contain exactly four encoded_args');
145
+ const [depositId, tokenContract, receiver, amount] = encodedArgs;
146
+ if (!/^[0-9a-f]{64}$/.test(depositId))
147
+ throw new Error('Stellar depository ID must be 32 hex-encoded bytes');
148
+ if (!/^[1-9]\d*$/.test(amount))
149
+ throw new Error('Stellar deposit amount is invalid');
150
+ if (!StrKey.isValidContract(tokenContract))
151
+ throw new Error('Stellar depository asset contract is invalid');
152
+ if (!StrKey.isValidEd25519PublicKey(receiver))
153
+ throw new Error('Stellar depository receiver is invalid');
154
+ const idBytes = Uint8Array.from({ length: 32 }, (_, index) => Number.parseInt(depositId.slice(index * 2, index * 2 + 2), 16));
155
+ const operation = Operation.invokeContractFunction({
156
+ contract: depositoryContract,
157
+ function: 'deposit',
158
+ source: selectedAddress,
159
+ args: [
160
+ new Address(selectedAddress).toScVal(),
161
+ nativeToScVal(idBytes),
162
+ new Address(tokenContract).toScVal(),
163
+ new Address(receiver).toScVal(),
164
+ nativeToScVal(BigInt(amount), { type: 'i128' }),
165
+ ],
166
+ auth: [],
167
+ });
168
+ validateDepositOperation(Operation.fromXdrObject(operation), params);
169
+ return operation;
170
+ }
171
+ export function validateStellarXdr(params) {
172
+ const { envelopeXdr, networkPassphrase, selectedAddress, depositoryContract, token, amountInBaseUnits, encodedArgs, swapSequenceNumber, currentAccountSequence, now = Math.floor(Date.now() / 1000), } = params;
173
+ validateAddresses(selectedAddress, depositoryContract);
174
+ if (!envelopeXdr)
175
+ throw new Error('Stellar deposit action is missing unsigned XDR');
176
+ const parsed = TransactionBuilder.fromXdr(envelopeXdr, networkPassphrase);
177
+ if (!(parsed instanceof Transaction))
178
+ throw new Error('Fee-bump Stellar transactions are not supported');
179
+ if (parsed.networkPassphrase !== networkPassphrase)
180
+ throw new Error('Stellar network passphrase mismatch');
181
+ if (parsed.signatures.length !== 0)
182
+ throw new Error('Stellar deposit XDR must be unsigned');
183
+ if (parsed.source !== selectedAddress)
184
+ throw new Error('Stellar transaction source does not match the connected account');
185
+ if (!/^[1-9]\d*$/.test(parsed.fee))
186
+ throw new Error('Stellar transaction fee is invalid');
187
+ if (parsed.memo.type !== 'none')
188
+ throw new Error('Stellar depository transaction cannot contain a memo');
189
+ if (parsed.ledgerBounds
190
+ || parsed.minAccountSequence !== undefined
191
+ || parsed.minAccountSequenceAge !== undefined
192
+ || parsed.minAccountSequenceLedgerGap !== undefined
193
+ || parsed.extraSigners !== undefined) {
194
+ throw new Error('Stellar depository transaction contains unexpected preconditions');
195
+ }
196
+ let expectedSequence;
197
+ try {
198
+ expectedSequence = (BigInt(currentAccountSequence) + 1n).toString();
199
+ }
200
+ catch {
201
+ throw new Error('Horizon returned an invalid Stellar account sequence');
202
+ }
203
+ if (parsed.sequence !== expectedSequence)
204
+ throw staleError('Stellar deposit action has a stale account sequence');
205
+ if (parsed.operations.length !== 1)
206
+ throw new Error('Stellar deposit XDR must contain exactly one operation');
207
+ const operation = parsed.operations[0];
208
+ const { tokenContract, receiver } = validateDepositOperation(operation, {
209
+ selectedAddress,
210
+ depositoryContract,
211
+ networkPassphrase,
212
+ token,
213
+ amountInBaseUnits,
214
+ encodedArgs,
215
+ swapSequenceNumber,
216
+ });
217
+ if (operation.type !== 'invokeHostFunction' || operation.func.type !== 'hostFunctionTypeInvokeContract') {
218
+ throw new Error('Stellar deposit XDR must contain one Soroban contract invocation');
219
+ }
220
+ const invocation = operation.func.invokeContract;
221
+ const authorization = operation.auth;
222
+ if (!authorization || authorization.length !== 1) {
223
+ throw new Error('Stellar depository transaction has unexpected authorization');
224
+ }
225
+ const entry = authorization[0];
226
+ if (entry.credentials.type !== 'sorobanCredentialsSourceAccount') {
227
+ throw new Error('Stellar depository transaction requires unsupported authorization');
228
+ }
229
+ const root = entry.rootInvocation;
230
+ if (root.function.type !== 'sorobanAuthorizedFunctionTypeContractFn' || root.subInvocations.length !== 1) {
231
+ throw new Error('Stellar depository authorization tree is invalid');
232
+ }
233
+ assertInvocation(root.function.contractFn, depositoryContract, 'deposit', invocation.args, 'deposit authorization');
234
+ const transfer = root.subInvocations[0];
235
+ if (transfer.function.type !== 'sorobanAuthorizedFunctionTypeContractFn' || transfer.subInvocations.length !== 0) {
236
+ throw new Error('Stellar asset authorization tree is invalid');
237
+ }
238
+ assertInvocation(transfer.function.contractFn, tokenContract, 'transfer', [invocation.args[0], invocation.args[3], invocation.args[4]], 'asset authorization');
239
+ if (readAddress(transfer.function.contractFn.args[1], 'authorized receiver') !== receiver) {
240
+ throw new Error('Stellar asset authorization receiver does not match the deposit');
241
+ }
242
+ const bounds = parsed.timeBounds;
243
+ if (!bounds || bounds.minTime !== '0' || bounds.maxTime === '0') {
244
+ throw new Error('Stellar deposit XDR must have bounded time conditions');
245
+ }
246
+ if (BigInt(bounds.maxTime) <= BigInt(now))
247
+ throw staleError('Stellar deposit action has expired');
248
+ return parsed;
249
+ }