@openzeppelin/adapter-stellar 1.0.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 +272 -0
- package/dist/config.cjs +21 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +8 -0
- package/dist/config.d.cts.map +1 -0
- package/dist/config.d.mts +8 -0
- package/dist/config.d.mts.map +1 -0
- package/dist/config.mjs +20 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.cjs +7564 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +261 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +263 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +7529 -0
- package/dist/index.mjs.map +1 -0
- package/dist/metadata.cjs +22 -0
- package/dist/metadata.cjs.map +1 -0
- package/dist/metadata.d.cts +7 -0
- package/dist/metadata.d.cts.map +1 -0
- package/dist/metadata.d.mts +7 -0
- package/dist/metadata.d.mts.map +1 -0
- package/dist/metadata.mjs +21 -0
- package/dist/metadata.mjs.map +1 -0
- package/dist/networks-BrV516-R.d.cts +15 -0
- package/dist/networks-BrV516-R.d.cts.map +1 -0
- package/dist/networks-C0MmhJcu.d.mts +15 -0
- package/dist/networks-C0MmhJcu.d.mts.map +1 -0
- package/dist/networks-DgUFSTiC.cjs +76 -0
- package/dist/networks-DgUFSTiC.cjs.map +1 -0
- package/dist/networks-QbEPbaGT.mjs +46 -0
- package/dist/networks-QbEPbaGT.mjs.map +1 -0
- package/dist/networks.cjs +8 -0
- package/dist/networks.d.cts +2 -0
- package/dist/networks.d.mts +2 -0
- package/dist/networks.mjs +3 -0
- package/dist/vite-config.cjs +43 -0
- package/dist/vite-config.cjs.map +1 -0
- package/dist/vite-config.d.cts +35 -0
- package/dist/vite-config.d.cts.map +1 -0
- package/dist/vite-config.d.mts +35 -0
- package/dist/vite-config.d.mts.map +1 -0
- package/dist/vite-config.mjs +42 -0
- package/dist/vite-config.mjs.map +1 -0
- package/package.json +114 -0
- package/src/__tests__/getDefaultServiceConfig.test.ts +105 -0
- package/src/access-control/actions.ts +214 -0
- package/src/access-control/feature-detection.ts +238 -0
- package/src/access-control/index.ts +54 -0
- package/src/access-control/indexer-client.ts +1474 -0
- package/src/access-control/onchain-reader.ts +446 -0
- package/src/access-control/service.ts +1431 -0
- package/src/access-control/validation.ts +256 -0
- package/src/adapter.ts +659 -0
- package/src/config.ts +43 -0
- package/src/configuration/__tests__/explorer.test.ts +80 -0
- package/src/configuration/__tests__/rpc.test.ts +355 -0
- package/src/configuration/execution.ts +83 -0
- package/src/configuration/explorer.ts +105 -0
- package/src/configuration/index.ts +5 -0
- package/src/configuration/network-services.ts +210 -0
- package/src/configuration/rpc.ts +270 -0
- package/src/configuration.ts +2 -0
- package/src/contract/__tests__/complete-type-coverage.test.ts +78 -0
- package/src/contract/index.ts +3 -0
- package/src/contract/loader.ts +498 -0
- package/src/contract/transformer.ts +1 -0
- package/src/contract/type.ts +65 -0
- package/src/index.ts +23 -0
- package/src/mapping/constants.ts +89 -0
- package/src/mapping/enum-metadata.ts +237 -0
- package/src/mapping/field-generator.ts +296 -0
- package/src/mapping/index.ts +5 -0
- package/src/mapping/struct-fields.ts +106 -0
- package/src/mapping/tuple-components.ts +43 -0
- package/src/mapping/type-coverage-validator.ts +151 -0
- package/src/mapping/type-mapper.ts +203 -0
- package/src/metadata.ts +16 -0
- package/src/networks/README.md +84 -0
- package/src/networks/index.ts +19 -0
- package/src/networks/mainnet.ts +20 -0
- package/src/networks/testnet.ts +20 -0
- package/src/networks.ts +2 -0
- package/src/query/handler.ts +411 -0
- package/src/query/index.ts +4 -0
- package/src/query/view-checker.ts +32 -0
- package/src/sac/spec-cache.ts +68 -0
- package/src/sac/spec-source.ts +35 -0
- package/src/sac/xdr.ts +101 -0
- package/src/transaction/components/AdvancedInfo.tsx +34 -0
- package/src/transaction/components/FeeConfiguration.tsx +41 -0
- package/src/transaction/components/StellarRelayerOptions.tsx +60 -0
- package/src/transaction/components/TransactionTiming.tsx +77 -0
- package/src/transaction/components/index.ts +5 -0
- package/src/transaction/components/useStellarRelayerOptions.ts +114 -0
- package/src/transaction/eoa.ts +229 -0
- package/src/transaction/execution-strategy.ts +33 -0
- package/src/transaction/formatter.ts +296 -0
- package/src/transaction/index.ts +4 -0
- package/src/transaction/relayer.ts +575 -0
- package/src/transaction/sender.ts +156 -0
- package/src/transform/index.ts +4 -0
- package/src/transform/input-parser.ts +9 -0
- package/src/transform/output-formatter.ts +133 -0
- package/src/transform/parsers/complex-parser.ts +157 -0
- package/src/transform/parsers/generic-parser.ts +171 -0
- package/src/transform/parsers/index.ts +86 -0
- package/src/transform/parsers/primitive-parser.ts +123 -0
- package/src/transform/parsers/scval-converter.ts +405 -0
- package/src/transform/parsers/struct-parser.ts +324 -0
- package/src/transform/parsers/types.ts +35 -0
- package/src/types/__tests__/artifacts.test.ts +89 -0
- package/src/types/artifacts.ts +19 -0
- package/src/utils/__tests__/artifacts.test.ts +77 -0
- package/src/utils/artifacts.ts +30 -0
- package/src/utils/formatting.ts +122 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/input-parsing.ts +336 -0
- package/src/utils/safe-type-parser.ts +303 -0
- package/src/utils/stellar-types.ts +35 -0
- package/src/utils/type-detection.ts +163 -0
- package/src/utils/xdr-ordering.ts +36 -0
- package/src/validation/__tests__/address.test.ts +267 -0
- package/src/validation/address.ts +136 -0
- package/src/validation/eoa.ts +33 -0
- package/src/validation/index.ts +3 -0
- package/src/validation/relayer.ts +13 -0
- package/src/vite-config.ts +67 -0
- package/src/wallet/README.md +93 -0
- package/src/wallet/__tests__/connection.test.ts +72 -0
- package/src/wallet/components/StellarWalletUiRoot.tsx +161 -0
- package/src/wallet/components/account/AccountDisplay.tsx +50 -0
- package/src/wallet/components/connect/ConnectButton.tsx +100 -0
- package/src/wallet/components/connect/ConnectorDialog.tsx +125 -0
- package/src/wallet/components/index.ts +3 -0
- package/src/wallet/connection.ts +151 -0
- package/src/wallet/context/StellarWalletContext.ts +32 -0
- package/src/wallet/context/index.ts +4 -0
- package/src/wallet/context/useStellarWalletContext.ts +17 -0
- package/src/wallet/hooks/facade-hooks.ts +31 -0
- package/src/wallet/hooks/index.ts +7 -0
- package/src/wallet/hooks/useStellarAccount.ts +27 -0
- package/src/wallet/hooks/useStellarConnect.ts +60 -0
- package/src/wallet/hooks/useStellarDisconnect.ts +47 -0
- package/src/wallet/hooks/useUiKitConfig.ts +40 -0
- package/src/wallet/implementation/wallets-kit-implementation.ts +379 -0
- package/src/wallet/index.ts +11 -0
- package/src/wallet/services/__tests__/configResolutionService.test.ts +163 -0
- package/src/wallet/services/configResolutionService.ts +65 -0
- package/src/wallet/stellar-wallets-kit/StellarWalletsKitConnectButton.tsx +82 -0
- package/src/wallet/stellar-wallets-kit/__mocks__/@creit.tech/stellar-wallets-kit.ts +48 -0
- package/src/wallet/stellar-wallets-kit/__tests__/export-service.test.ts +93 -0
- package/src/wallet/stellar-wallets-kit/__tests__/stellarUiKitManager.test.ts +0 -0
- package/src/wallet/stellar-wallets-kit/config-generator.ts +75 -0
- package/src/wallet/stellar-wallets-kit/export-service.ts +19 -0
- package/src/wallet/stellar-wallets-kit/index.ts +3 -0
- package/src/wallet/stellar-wallets-kit/stellarUiKitManager.ts +235 -0
- package/src/wallet/types.ts +19 -0
- package/src/wallet/utils/__tests__/filterWalletComponents.test.ts +150 -0
- package/src/wallet/utils/__tests__/uiKitService.test.ts +189 -0
- package/src/wallet/utils/filterWalletComponents.ts +89 -0
- package/src/wallet/utils/index.ts +3 -0
- package/src/wallet/utils/stellarWalletImplementationManager.ts +118 -0
- package/src/wallet/utils/uiKitService.ts +74 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-Chain Reader Module
|
|
3
|
+
*
|
|
4
|
+
* Reads current access control state (ownership and roles) from Stellar (Soroban) contracts
|
|
5
|
+
* using on-chain queries. Supports both Ownable and AccessControl patterns.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { rpc as StellarRpc } from '@stellar/stellar-sdk';
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
ContractSchema,
|
|
12
|
+
OwnershipInfo,
|
|
13
|
+
RoleAssignment,
|
|
14
|
+
RoleIdentifier,
|
|
15
|
+
StellarNetworkConfig,
|
|
16
|
+
} from '@openzeppelin/ui-types';
|
|
17
|
+
import { OperationFailed } from '@openzeppelin/ui-types';
|
|
18
|
+
import { DEFAULT_CONCURRENCY_LIMIT, logger, promiseAllWithLimit } from '@openzeppelin/ui-utils';
|
|
19
|
+
|
|
20
|
+
import { queryStellarViewFunction } from '../query/handler';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Helper to load a minimal contract schema for access control functions
|
|
24
|
+
* This allows us to use the existing query infrastructure
|
|
25
|
+
*/
|
|
26
|
+
function createMinimalSchema(
|
|
27
|
+
contractAddress: string,
|
|
28
|
+
functionName: string,
|
|
29
|
+
inputs: Array<{ name: string; type: string }> = [],
|
|
30
|
+
outputType = 'Val'
|
|
31
|
+
): ContractSchema {
|
|
32
|
+
return {
|
|
33
|
+
ecosystem: 'stellar',
|
|
34
|
+
address: contractAddress,
|
|
35
|
+
functions: [
|
|
36
|
+
{
|
|
37
|
+
id: functionName,
|
|
38
|
+
name: functionName,
|
|
39
|
+
displayName: functionName,
|
|
40
|
+
type: 'function',
|
|
41
|
+
inputs,
|
|
42
|
+
outputs: [{ name: 'result', type: outputType }],
|
|
43
|
+
modifiesState: false,
|
|
44
|
+
stateMutability: 'view',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Simple wrapper around queryStellarViewFunction for access control queries
|
|
52
|
+
*/
|
|
53
|
+
async function queryAccessControlFunction(
|
|
54
|
+
contractAddress: string,
|
|
55
|
+
functionName: string,
|
|
56
|
+
params: unknown[],
|
|
57
|
+
networkConfig: StellarNetworkConfig,
|
|
58
|
+
inputs: Array<{ name: string; type: string }> = []
|
|
59
|
+
): Promise<unknown> {
|
|
60
|
+
const schema = createMinimalSchema(contractAddress, functionName, inputs);
|
|
61
|
+
return queryStellarViewFunction(contractAddress, functionName, networkConfig, params, schema);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Reads the current owner from an Ownable contract
|
|
66
|
+
*
|
|
67
|
+
* @param contractAddress The contract address
|
|
68
|
+
* @param networkConfig The network configuration
|
|
69
|
+
* @returns Ownership information
|
|
70
|
+
*/
|
|
71
|
+
export async function readOwnership(
|
|
72
|
+
contractAddress: string,
|
|
73
|
+
networkConfig: StellarNetworkConfig
|
|
74
|
+
): Promise<OwnershipInfo> {
|
|
75
|
+
logger.info('readOwnership', `Reading owner for contract ${contractAddress}`);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const result = await queryAccessControlFunction(
|
|
79
|
+
contractAddress,
|
|
80
|
+
'get_owner',
|
|
81
|
+
[],
|
|
82
|
+
networkConfig
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// get_owner returns Option<Address>
|
|
86
|
+
if (result === undefined || result === null) {
|
|
87
|
+
return { owner: null };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const ownerAddress = typeof result === 'string' ? result : String(result);
|
|
91
|
+
logger.debug('readOwnership', `Owner: ${ownerAddress}`);
|
|
92
|
+
|
|
93
|
+
return { owner: ownerAddress };
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.error('readOwnership', 'Failed to read ownership:', error);
|
|
96
|
+
throw new OperationFailed(
|
|
97
|
+
`Failed to read ownership: ${(error as Error).message}`,
|
|
98
|
+
contractAddress,
|
|
99
|
+
'readOwnership',
|
|
100
|
+
error as Error
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Checks if an account has a specific role
|
|
107
|
+
*
|
|
108
|
+
* @param contractAddress The contract address
|
|
109
|
+
* @param roleId The role identifier (Symbol)
|
|
110
|
+
* @param account The account address to check
|
|
111
|
+
* @param networkConfig The network configuration
|
|
112
|
+
* @returns True if the account has the role, false otherwise
|
|
113
|
+
*/
|
|
114
|
+
export async function hasRole(
|
|
115
|
+
contractAddress: string,
|
|
116
|
+
roleId: string,
|
|
117
|
+
account: string,
|
|
118
|
+
networkConfig: StellarNetworkConfig
|
|
119
|
+
): Promise<boolean> {
|
|
120
|
+
logger.debug('hasRole', `Checking role ${roleId} for ${account}`);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const inputs = [
|
|
124
|
+
{ name: 'account', type: 'Address' },
|
|
125
|
+
{ name: 'role', type: 'Symbol' },
|
|
126
|
+
];
|
|
127
|
+
const result = await queryAccessControlFunction(
|
|
128
|
+
contractAddress,
|
|
129
|
+
'has_role',
|
|
130
|
+
[account, roleId],
|
|
131
|
+
networkConfig,
|
|
132
|
+
inputs
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// has_role returns Option<u32> (Some(index) if has role, None otherwise)
|
|
136
|
+
return typeof result === 'number';
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger.error('hasRole', `Failed to check role ${roleId}:`, error);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Gets the count of members for a specific role
|
|
145
|
+
*
|
|
146
|
+
* @param contractAddress The contract address
|
|
147
|
+
* @param roleId The role identifier (Symbol)
|
|
148
|
+
* @param networkConfig The network configuration
|
|
149
|
+
* @returns The count of role members
|
|
150
|
+
*/
|
|
151
|
+
export async function getRoleMemberCount(
|
|
152
|
+
contractAddress: string,
|
|
153
|
+
roleId: string,
|
|
154
|
+
networkConfig: StellarNetworkConfig
|
|
155
|
+
): Promise<number> {
|
|
156
|
+
logger.debug('getRoleMemberCount', `Getting member count for role ${roleId}`);
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const inputs = [{ name: 'role', type: 'Symbol' }];
|
|
160
|
+
const result = await queryAccessControlFunction(
|
|
161
|
+
contractAddress,
|
|
162
|
+
'get_role_member_count',
|
|
163
|
+
[roleId],
|
|
164
|
+
networkConfig,
|
|
165
|
+
inputs
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Handle both number and string results (formatter may return string for large numbers)
|
|
169
|
+
if (typeof result === 'number') {
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
if (typeof result === 'string') {
|
|
173
|
+
const parsed = parseInt(result, 10);
|
|
174
|
+
return isNaN(parsed) ? 0 : parsed;
|
|
175
|
+
}
|
|
176
|
+
return 0;
|
|
177
|
+
} catch (error) {
|
|
178
|
+
logger.error('getRoleMemberCount', `Failed to get member count for role ${roleId}:`, error);
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Gets the member address at a specific index for a role
|
|
185
|
+
*
|
|
186
|
+
* @param contractAddress The contract address
|
|
187
|
+
* @param roleId The role identifier (Symbol)
|
|
188
|
+
* @param index The index of the member to retrieve
|
|
189
|
+
* @param networkConfig The network configuration
|
|
190
|
+
* @returns The member address, or null if index out of bounds
|
|
191
|
+
*/
|
|
192
|
+
export async function getRoleMember(
|
|
193
|
+
contractAddress: string,
|
|
194
|
+
roleId: string,
|
|
195
|
+
index: number,
|
|
196
|
+
networkConfig: StellarNetworkConfig
|
|
197
|
+
): Promise<string | null> {
|
|
198
|
+
logger.debug('getRoleMember', `Getting member at index ${index} for role ${roleId}`);
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const inputs = [
|
|
202
|
+
{ name: 'role', type: 'Symbol' },
|
|
203
|
+
{ name: 'index', type: 'u32' },
|
|
204
|
+
];
|
|
205
|
+
const result = await queryAccessControlFunction(
|
|
206
|
+
contractAddress,
|
|
207
|
+
'get_role_member',
|
|
208
|
+
[roleId, index],
|
|
209
|
+
networkConfig,
|
|
210
|
+
inputs
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (result === undefined || result === null) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return String(result);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
logger.error('getRoleMember', `Failed to get role member at index ${index}:`, error);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Enumerates all members of a specific role
|
|
226
|
+
*
|
|
227
|
+
* Fetches all members in parallel with controlled concurrency for improved
|
|
228
|
+
* performance while avoiding overwhelming RPC endpoints.
|
|
229
|
+
*
|
|
230
|
+
* @param contractAddress The contract address
|
|
231
|
+
* @param roleId The role identifier (Symbol)
|
|
232
|
+
* @param networkConfig The network configuration
|
|
233
|
+
* @returns Array of member addresses
|
|
234
|
+
*/
|
|
235
|
+
export async function enumerateRoleMembers(
|
|
236
|
+
contractAddress: string,
|
|
237
|
+
roleId: string,
|
|
238
|
+
networkConfig: StellarNetworkConfig
|
|
239
|
+
): Promise<string[]> {
|
|
240
|
+
logger.info('enumerateRoleMembers', `Enumerating members for role ${roleId}`);
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
// Get the count of members
|
|
244
|
+
const count = await getRoleMemberCount(contractAddress, roleId, networkConfig);
|
|
245
|
+
|
|
246
|
+
logger.debug('enumerateRoleMembers', `Role ${roleId} has ${count} members`);
|
|
247
|
+
|
|
248
|
+
if (count === 0) {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Create task functions for controlled concurrent execution
|
|
253
|
+
const memberTasks = Array.from(
|
|
254
|
+
{ length: count },
|
|
255
|
+
(_, i) => () => getRoleMember(contractAddress, roleId, i, networkConfig)
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Fetch members with concurrency limit to avoid overwhelming RPC endpoints
|
|
259
|
+
const results = await promiseAllWithLimit(memberTasks, DEFAULT_CONCURRENCY_LIMIT);
|
|
260
|
+
|
|
261
|
+
// Filter out null results and return valid members
|
|
262
|
+
const members = results.filter((m): m is string => m !== null);
|
|
263
|
+
|
|
264
|
+
logger.debug('enumerateRoleMembers', `Retrieved ${members.length} members for role ${roleId}`);
|
|
265
|
+
|
|
266
|
+
return members;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.error('enumerateRoleMembers', `Failed to enumerate role ${roleId}:`, error);
|
|
269
|
+
throw new OperationFailed(
|
|
270
|
+
`Failed to enumerate role members: ${(error as Error).message}`,
|
|
271
|
+
contractAddress,
|
|
272
|
+
'enumerateRoleMembers',
|
|
273
|
+
error as Error
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Reads all current role assignments for a contract
|
|
280
|
+
*
|
|
281
|
+
* Fetches all roles in parallel for improved performance.
|
|
282
|
+
*
|
|
283
|
+
* @param contractAddress The contract address
|
|
284
|
+
* @param roleIds Array of role identifiers to query
|
|
285
|
+
* @param networkConfig The network configuration
|
|
286
|
+
* @returns Array of role assignments
|
|
287
|
+
*/
|
|
288
|
+
export async function readCurrentRoles(
|
|
289
|
+
contractAddress: string,
|
|
290
|
+
roleIds: string[],
|
|
291
|
+
networkConfig: StellarNetworkConfig
|
|
292
|
+
): Promise<RoleAssignment[]> {
|
|
293
|
+
logger.info(
|
|
294
|
+
'readCurrentRoles',
|
|
295
|
+
`Reading ${roleIds.length} roles for contract ${contractAddress}`
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (roleIds.length === 0) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Process all roles in parallel for improved performance
|
|
303
|
+
const assignmentPromises = roleIds.map(async (roleId) => {
|
|
304
|
+
const role: RoleIdentifier = {
|
|
305
|
+
id: roleId,
|
|
306
|
+
label: roleId.replace(/_/g, ' ').toLowerCase(),
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const members = await enumerateRoleMembers(contractAddress, roleId, networkConfig);
|
|
311
|
+
|
|
312
|
+
logger.debug('readCurrentRoles', `Role ${roleId} has ${members.length} members`);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
role,
|
|
316
|
+
members,
|
|
317
|
+
};
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logger.warn('readCurrentRoles', `Failed to read role ${roleId}:`, error);
|
|
320
|
+
// Return role with empty members array to maintain array length consistency
|
|
321
|
+
return {
|
|
322
|
+
role,
|
|
323
|
+
members: [],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const assignments = await Promise.all(assignmentPromises);
|
|
329
|
+
|
|
330
|
+
logger.info(
|
|
331
|
+
'readCurrentRoles',
|
|
332
|
+
`Completed reading ${assignments.length} roles with ${assignments.reduce((sum, a) => sum + a.members.length, 0)} total members`
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
return assignments;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Gets the admin role for a specific role
|
|
340
|
+
*
|
|
341
|
+
* @param contractAddress The contract address
|
|
342
|
+
* @param roleId The role identifier (Symbol)
|
|
343
|
+
* @param networkConfig The network configuration
|
|
344
|
+
* @returns The admin role identifier, or null if no admin role set
|
|
345
|
+
*/
|
|
346
|
+
export async function getRoleAdmin(
|
|
347
|
+
contractAddress: string,
|
|
348
|
+
roleId: string,
|
|
349
|
+
networkConfig: StellarNetworkConfig
|
|
350
|
+
): Promise<string | null> {
|
|
351
|
+
logger.debug('getRoleAdmin', `Getting admin role for ${roleId}`);
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const inputs = [{ name: 'role', type: 'Symbol' }];
|
|
355
|
+
const result = await queryAccessControlFunction(
|
|
356
|
+
contractAddress,
|
|
357
|
+
'get_role_admin',
|
|
358
|
+
[roleId],
|
|
359
|
+
networkConfig,
|
|
360
|
+
inputs
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
// get_role_admin returns Option<Symbol>
|
|
364
|
+
if (result === undefined || result === null) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return String(result);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
logger.error('getRoleAdmin', `Failed to get admin role for ${roleId}:`, error);
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Gets the top-level admin account
|
|
377
|
+
*
|
|
378
|
+
* @param contractAddress The contract address
|
|
379
|
+
* @param networkConfig The network configuration
|
|
380
|
+
* @returns The admin address, or null if no admin set
|
|
381
|
+
*/
|
|
382
|
+
export async function getAdmin(
|
|
383
|
+
contractAddress: string,
|
|
384
|
+
networkConfig: StellarNetworkConfig
|
|
385
|
+
): Promise<string | null> {
|
|
386
|
+
logger.info('getAdmin', `Reading admin for contract ${contractAddress}`);
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const result = await queryAccessControlFunction(
|
|
390
|
+
contractAddress,
|
|
391
|
+
'get_admin',
|
|
392
|
+
[],
|
|
393
|
+
networkConfig
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// get_admin returns Option<Address>
|
|
397
|
+
if (result === undefined || result === null) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return String(result);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
logger.error('getAdmin', 'Failed to read admin:', error);
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Gets the current ledger sequence number from the Soroban RPC
|
|
410
|
+
*
|
|
411
|
+
* Used for two-step Ownable contracts to:
|
|
412
|
+
* - Calculate appropriate expiration ledgers for ownership transfers
|
|
413
|
+
* - Validate expiration ledgers before submitting transactions
|
|
414
|
+
* - Determine if pending ownership transfers have expired
|
|
415
|
+
*
|
|
416
|
+
* @param networkConfig The network configuration containing the Soroban RPC URL
|
|
417
|
+
* @returns Promise resolving to the current ledger sequence number
|
|
418
|
+
* @throws OperationFailed if the RPC call fails
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* ```typescript
|
|
422
|
+
* const currentLedger = await getCurrentLedger(networkConfig);
|
|
423
|
+
* // Set expiration to ~1 hour from now (~720 ledgers at 5s/ledger)
|
|
424
|
+
* const expirationLedger = currentLedger + 720;
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
export async function getCurrentLedger(networkConfig: StellarNetworkConfig): Promise<number> {
|
|
428
|
+
logger.info('getCurrentLedger', `Fetching current ledger from ${networkConfig.sorobanRpcUrl}`);
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
const server = new StellarRpc.Server(networkConfig.sorobanRpcUrl);
|
|
432
|
+
const latestLedger = await server.getLatestLedger();
|
|
433
|
+
|
|
434
|
+
logger.debug('getCurrentLedger', `Current ledger: ${latestLedger.sequence}`);
|
|
435
|
+
|
|
436
|
+
return latestLedger.sequence;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
logger.error('getCurrentLedger', 'Failed to fetch current ledger:', error);
|
|
439
|
+
throw new OperationFailed(
|
|
440
|
+
`Failed to get current ledger: ${(error as Error).message}`,
|
|
441
|
+
networkConfig.sorobanRpcUrl,
|
|
442
|
+
'getCurrentLedger',
|
|
443
|
+
error as Error
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|