@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.
Files changed (165) hide show
  1. package/README.md +272 -0
  2. package/dist/config.cjs +21 -0
  3. package/dist/config.cjs.map +1 -0
  4. package/dist/config.d.cts +8 -0
  5. package/dist/config.d.cts.map +1 -0
  6. package/dist/config.d.mts +8 -0
  7. package/dist/config.d.mts.map +1 -0
  8. package/dist/config.mjs +20 -0
  9. package/dist/config.mjs.map +1 -0
  10. package/dist/index.cjs +7564 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +261 -0
  13. package/dist/index.d.cts.map +1 -0
  14. package/dist/index.d.mts +263 -0
  15. package/dist/index.d.mts.map +1 -0
  16. package/dist/index.mjs +7529 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/metadata.cjs +22 -0
  19. package/dist/metadata.cjs.map +1 -0
  20. package/dist/metadata.d.cts +7 -0
  21. package/dist/metadata.d.cts.map +1 -0
  22. package/dist/metadata.d.mts +7 -0
  23. package/dist/metadata.d.mts.map +1 -0
  24. package/dist/metadata.mjs +21 -0
  25. package/dist/metadata.mjs.map +1 -0
  26. package/dist/networks-BrV516-R.d.cts +15 -0
  27. package/dist/networks-BrV516-R.d.cts.map +1 -0
  28. package/dist/networks-C0MmhJcu.d.mts +15 -0
  29. package/dist/networks-C0MmhJcu.d.mts.map +1 -0
  30. package/dist/networks-DgUFSTiC.cjs +76 -0
  31. package/dist/networks-DgUFSTiC.cjs.map +1 -0
  32. package/dist/networks-QbEPbaGT.mjs +46 -0
  33. package/dist/networks-QbEPbaGT.mjs.map +1 -0
  34. package/dist/networks.cjs +8 -0
  35. package/dist/networks.d.cts +2 -0
  36. package/dist/networks.d.mts +2 -0
  37. package/dist/networks.mjs +3 -0
  38. package/dist/vite-config.cjs +43 -0
  39. package/dist/vite-config.cjs.map +1 -0
  40. package/dist/vite-config.d.cts +35 -0
  41. package/dist/vite-config.d.cts.map +1 -0
  42. package/dist/vite-config.d.mts +35 -0
  43. package/dist/vite-config.d.mts.map +1 -0
  44. package/dist/vite-config.mjs +42 -0
  45. package/dist/vite-config.mjs.map +1 -0
  46. package/package.json +114 -0
  47. package/src/__tests__/getDefaultServiceConfig.test.ts +105 -0
  48. package/src/access-control/actions.ts +214 -0
  49. package/src/access-control/feature-detection.ts +238 -0
  50. package/src/access-control/index.ts +54 -0
  51. package/src/access-control/indexer-client.ts +1474 -0
  52. package/src/access-control/onchain-reader.ts +446 -0
  53. package/src/access-control/service.ts +1431 -0
  54. package/src/access-control/validation.ts +256 -0
  55. package/src/adapter.ts +659 -0
  56. package/src/config.ts +43 -0
  57. package/src/configuration/__tests__/explorer.test.ts +80 -0
  58. package/src/configuration/__tests__/rpc.test.ts +355 -0
  59. package/src/configuration/execution.ts +83 -0
  60. package/src/configuration/explorer.ts +105 -0
  61. package/src/configuration/index.ts +5 -0
  62. package/src/configuration/network-services.ts +210 -0
  63. package/src/configuration/rpc.ts +270 -0
  64. package/src/configuration.ts +2 -0
  65. package/src/contract/__tests__/complete-type-coverage.test.ts +78 -0
  66. package/src/contract/index.ts +3 -0
  67. package/src/contract/loader.ts +498 -0
  68. package/src/contract/transformer.ts +1 -0
  69. package/src/contract/type.ts +65 -0
  70. package/src/index.ts +23 -0
  71. package/src/mapping/constants.ts +89 -0
  72. package/src/mapping/enum-metadata.ts +237 -0
  73. package/src/mapping/field-generator.ts +296 -0
  74. package/src/mapping/index.ts +5 -0
  75. package/src/mapping/struct-fields.ts +106 -0
  76. package/src/mapping/tuple-components.ts +43 -0
  77. package/src/mapping/type-coverage-validator.ts +151 -0
  78. package/src/mapping/type-mapper.ts +203 -0
  79. package/src/metadata.ts +16 -0
  80. package/src/networks/README.md +84 -0
  81. package/src/networks/index.ts +19 -0
  82. package/src/networks/mainnet.ts +20 -0
  83. package/src/networks/testnet.ts +20 -0
  84. package/src/networks.ts +2 -0
  85. package/src/query/handler.ts +411 -0
  86. package/src/query/index.ts +4 -0
  87. package/src/query/view-checker.ts +32 -0
  88. package/src/sac/spec-cache.ts +68 -0
  89. package/src/sac/spec-source.ts +35 -0
  90. package/src/sac/xdr.ts +101 -0
  91. package/src/transaction/components/AdvancedInfo.tsx +34 -0
  92. package/src/transaction/components/FeeConfiguration.tsx +41 -0
  93. package/src/transaction/components/StellarRelayerOptions.tsx +60 -0
  94. package/src/transaction/components/TransactionTiming.tsx +77 -0
  95. package/src/transaction/components/index.ts +5 -0
  96. package/src/transaction/components/useStellarRelayerOptions.ts +114 -0
  97. package/src/transaction/eoa.ts +229 -0
  98. package/src/transaction/execution-strategy.ts +33 -0
  99. package/src/transaction/formatter.ts +296 -0
  100. package/src/transaction/index.ts +4 -0
  101. package/src/transaction/relayer.ts +575 -0
  102. package/src/transaction/sender.ts +156 -0
  103. package/src/transform/index.ts +4 -0
  104. package/src/transform/input-parser.ts +9 -0
  105. package/src/transform/output-formatter.ts +133 -0
  106. package/src/transform/parsers/complex-parser.ts +157 -0
  107. package/src/transform/parsers/generic-parser.ts +171 -0
  108. package/src/transform/parsers/index.ts +86 -0
  109. package/src/transform/parsers/primitive-parser.ts +123 -0
  110. package/src/transform/parsers/scval-converter.ts +405 -0
  111. package/src/transform/parsers/struct-parser.ts +324 -0
  112. package/src/transform/parsers/types.ts +35 -0
  113. package/src/types/__tests__/artifacts.test.ts +89 -0
  114. package/src/types/artifacts.ts +19 -0
  115. package/src/utils/__tests__/artifacts.test.ts +77 -0
  116. package/src/utils/artifacts.ts +30 -0
  117. package/src/utils/formatting.ts +122 -0
  118. package/src/utils/index.ts +6 -0
  119. package/src/utils/input-parsing.ts +336 -0
  120. package/src/utils/safe-type-parser.ts +303 -0
  121. package/src/utils/stellar-types.ts +35 -0
  122. package/src/utils/type-detection.ts +163 -0
  123. package/src/utils/xdr-ordering.ts +36 -0
  124. package/src/validation/__tests__/address.test.ts +267 -0
  125. package/src/validation/address.ts +136 -0
  126. package/src/validation/eoa.ts +33 -0
  127. package/src/validation/index.ts +3 -0
  128. package/src/validation/relayer.ts +13 -0
  129. package/src/vite-config.ts +67 -0
  130. package/src/wallet/README.md +93 -0
  131. package/src/wallet/__tests__/connection.test.ts +72 -0
  132. package/src/wallet/components/StellarWalletUiRoot.tsx +161 -0
  133. package/src/wallet/components/account/AccountDisplay.tsx +50 -0
  134. package/src/wallet/components/connect/ConnectButton.tsx +100 -0
  135. package/src/wallet/components/connect/ConnectorDialog.tsx +125 -0
  136. package/src/wallet/components/index.ts +3 -0
  137. package/src/wallet/connection.ts +151 -0
  138. package/src/wallet/context/StellarWalletContext.ts +32 -0
  139. package/src/wallet/context/index.ts +4 -0
  140. package/src/wallet/context/useStellarWalletContext.ts +17 -0
  141. package/src/wallet/hooks/facade-hooks.ts +31 -0
  142. package/src/wallet/hooks/index.ts +7 -0
  143. package/src/wallet/hooks/useStellarAccount.ts +27 -0
  144. package/src/wallet/hooks/useStellarConnect.ts +60 -0
  145. package/src/wallet/hooks/useStellarDisconnect.ts +47 -0
  146. package/src/wallet/hooks/useUiKitConfig.ts +40 -0
  147. package/src/wallet/implementation/wallets-kit-implementation.ts +379 -0
  148. package/src/wallet/index.ts +11 -0
  149. package/src/wallet/services/__tests__/configResolutionService.test.ts +163 -0
  150. package/src/wallet/services/configResolutionService.ts +65 -0
  151. package/src/wallet/stellar-wallets-kit/StellarWalletsKitConnectButton.tsx +82 -0
  152. package/src/wallet/stellar-wallets-kit/__mocks__/@creit.tech/stellar-wallets-kit.ts +48 -0
  153. package/src/wallet/stellar-wallets-kit/__tests__/export-service.test.ts +93 -0
  154. package/src/wallet/stellar-wallets-kit/__tests__/stellarUiKitManager.test.ts +0 -0
  155. package/src/wallet/stellar-wallets-kit/config-generator.ts +75 -0
  156. package/src/wallet/stellar-wallets-kit/export-service.ts +19 -0
  157. package/src/wallet/stellar-wallets-kit/index.ts +3 -0
  158. package/src/wallet/stellar-wallets-kit/stellarUiKitManager.ts +235 -0
  159. package/src/wallet/types.ts +19 -0
  160. package/src/wallet/utils/__tests__/filterWalletComponents.test.ts +150 -0
  161. package/src/wallet/utils/__tests__/uiKitService.test.ts +189 -0
  162. package/src/wallet/utils/filterWalletComponents.ts +89 -0
  163. package/src/wallet/utils/index.ts +3 -0
  164. package/src/wallet/utils/stellarWalletImplementationManager.ts +118 -0
  165. package/src/wallet/utils/uiKitService.ts +74 -0
@@ -0,0 +1,105 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { StellarNetworkConfig } from '@openzeppelin/ui-types';
4
+
5
+ import { getStellarDefaultServiceConfig } from '../configuration/network-services';
6
+
7
+ /**
8
+ * Tests for getStellarDefaultServiceConfig function.
9
+ *
10
+ * Note: We test the pure function directly instead of instantiating StellarAdapter
11
+ * to avoid loading heavy Stellar SDK dependencies (WASM modules, etc.) which can
12
+ * cause memory issues during test execution.
13
+ */
14
+ describe('getStellarDefaultServiceConfig', () => {
15
+ const createMockNetworkConfig = (
16
+ overrides: Partial<StellarNetworkConfig> = {}
17
+ ): StellarNetworkConfig =>
18
+ ({
19
+ id: 'stellar-testnet',
20
+ exportConstName: 'stellarTestnet',
21
+ name: 'Stellar Testnet',
22
+ ecosystem: 'stellar',
23
+ network: 'testnet',
24
+ type: 'testnet',
25
+ isTestnet: true,
26
+ sorobanRpcUrl: 'https://soroban-testnet.stellar.org',
27
+ networkPassphrase: 'Test SDF Network ; September 2015',
28
+ ...overrides,
29
+ }) as StellarNetworkConfig;
30
+
31
+ describe('rpc service', () => {
32
+ it('should return RPC config when sorobanRpcUrl is present', () => {
33
+ const networkConfig = createMockNetworkConfig();
34
+
35
+ const result = getStellarDefaultServiceConfig(networkConfig, 'rpc');
36
+
37
+ expect(result).toEqual({
38
+ sorobanRpcUrl: 'https://soroban-testnet.stellar.org',
39
+ });
40
+ });
41
+
42
+ it('should return null when sorobanRpcUrl is missing', () => {
43
+ const networkConfig = createMockNetworkConfig({
44
+ sorobanRpcUrl: undefined,
45
+ });
46
+
47
+ const result = getStellarDefaultServiceConfig(networkConfig, 'rpc');
48
+
49
+ expect(result).toBeNull();
50
+ });
51
+ });
52
+
53
+ describe('access-control-indexer service', () => {
54
+ it('should return indexer config when both URLs are present', () => {
55
+ const networkConfig = createMockNetworkConfig({
56
+ indexerUri: 'https://indexer.stellar.example/graphql',
57
+ indexerWsUri: 'wss://indexer.stellar.example/graphql',
58
+ });
59
+
60
+ const result = getStellarDefaultServiceConfig(networkConfig, 'access-control-indexer');
61
+
62
+ expect(result).toEqual({
63
+ indexerUri: 'https://indexer.stellar.example/graphql',
64
+ indexerWsUri: 'wss://indexer.stellar.example/graphql',
65
+ });
66
+ });
67
+
68
+ it('should return null when indexerUri is missing', () => {
69
+ const networkConfig = createMockNetworkConfig({
70
+ indexerWsUri: 'wss://indexer.stellar.example/graphql',
71
+ });
72
+
73
+ const result = getStellarDefaultServiceConfig(networkConfig, 'access-control-indexer');
74
+
75
+ expect(result).toBeNull();
76
+ });
77
+
78
+ it('should return null when indexerWsUri is missing', () => {
79
+ const networkConfig = createMockNetworkConfig({
80
+ indexerUri: 'https://indexer.stellar.example/graphql',
81
+ });
82
+
83
+ const result = getStellarDefaultServiceConfig(networkConfig, 'access-control-indexer');
84
+
85
+ expect(result).toBeNull();
86
+ });
87
+
88
+ it('should return null when neither indexer URL is present', () => {
89
+ const networkConfig = createMockNetworkConfig();
90
+
91
+ const result = getStellarDefaultServiceConfig(networkConfig, 'access-control-indexer');
92
+
93
+ expect(result).toBeNull();
94
+ });
95
+ });
96
+
97
+ describe('unknown service', () => {
98
+ it('should return null for unknown service IDs', () => {
99
+ const networkConfig = createMockNetworkConfig();
100
+
101
+ expect(getStellarDefaultServiceConfig(networkConfig, 'explorer')).toBeNull();
102
+ expect(getStellarDefaultServiceConfig(networkConfig, 'unknown')).toBeNull();
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Access Control Actions Module
3
+ *
4
+ * Assembles transaction data for access control operations (grant/revoke roles, transfer ownership)
5
+ * on Stellar (Soroban) contracts. These actions prepare transaction data that can be executed
6
+ * via the standard Stellar transaction execution flow.
7
+ */
8
+
9
+ import { logger } from '@openzeppelin/ui-utils';
10
+
11
+ import type { StellarTransactionData } from '../transaction/formatter';
12
+
13
+ /**
14
+ * Special placeholder value that gets replaced with the connected wallet address
15
+ * at transaction execution time. Used when the caller parameter should be the
16
+ * transaction sender (connected wallet).
17
+ *
18
+ * @see EoaExecutionStrategy - replaces this placeholder before building the transaction
19
+ * @see RelayerExecutionStrategy - also replaces this placeholder before building the transaction
20
+ */
21
+ export const CALLER_PLACEHOLDER = '__CALLER__';
22
+
23
+ /**
24
+ * Assembles transaction data for granting a role to an account
25
+ *
26
+ * Note: OpenZeppelin Stellar AccessControl (v0.5.x) requires a `caller` parameter for authorization.
27
+ * The caller is the address authorizing the role grant (must have admin privileges).
28
+ * Use CALLER_PLACEHOLDER to use the connected wallet address as the caller.
29
+ *
30
+ * @param contractAddress The contract address
31
+ * @param roleId The role identifier (Symbol)
32
+ * @param account The account address to grant the role to
33
+ * @param caller The address authorizing the grant (defaults to CALLER_PLACEHOLDER for connected wallet)
34
+ * @returns Transaction data ready for execution
35
+ */
36
+ export function assembleGrantRoleAction(
37
+ contractAddress: string,
38
+ roleId: string,
39
+ account: string,
40
+ caller: string = CALLER_PLACEHOLDER
41
+ ): StellarTransactionData {
42
+ logger.info(
43
+ 'assembleGrantRoleAction',
44
+ `Assembling grant_role action for ${roleId} to ${account} (caller: ${caller})`
45
+ );
46
+
47
+ // Arguments for grant_role(caller: Address, account: Address, role: Symbol) - v0.5.x signature
48
+ // Note: args are raw values that will be converted to ScVal by the transaction execution flow
49
+ // The caller parameter is required by OpenZeppelin Stellar AccessControl for authorization
50
+ return {
51
+ contractAddress,
52
+ functionName: 'grant_role',
53
+ args: [caller, account, roleId],
54
+ argTypes: ['Address', 'Address', 'Symbol'],
55
+ argSchema: undefined,
56
+ transactionOptions: {},
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Assembles transaction data for revoking a role from an account
62
+ *
63
+ * Note: OpenZeppelin Stellar AccessControl (v0.5.x) requires a `caller` parameter for authorization.
64
+ * The caller is the address authorizing the role revocation (must have admin privileges).
65
+ * Use CALLER_PLACEHOLDER to use the connected wallet address as the caller.
66
+ *
67
+ * @param contractAddress The contract address
68
+ * @param roleId The role identifier (Symbol)
69
+ * @param account The account address to revoke the role from
70
+ * @param caller The address authorizing the revocation (defaults to CALLER_PLACEHOLDER for connected wallet)
71
+ * @returns Transaction data ready for execution
72
+ */
73
+ export function assembleRevokeRoleAction(
74
+ contractAddress: string,
75
+ roleId: string,
76
+ account: string,
77
+ caller: string = CALLER_PLACEHOLDER
78
+ ): StellarTransactionData {
79
+ logger.info(
80
+ 'assembleRevokeRoleAction',
81
+ `Assembling revoke_role action for ${roleId} from ${account} (caller: ${caller})`
82
+ );
83
+
84
+ // Arguments for revoke_role(caller: Address, account: Address, role: Symbol) - v0.5.x signature
85
+ // Note: args are raw values that will be converted to ScVal by the transaction execution flow
86
+ // The caller parameter is required by OpenZeppelin Stellar AccessControl for authorization
87
+ return {
88
+ contractAddress,
89
+ functionName: 'revoke_role',
90
+ args: [caller, account, roleId],
91
+ argTypes: ['Address', 'Address', 'Symbol'],
92
+ argSchema: undefined,
93
+ transactionOptions: {},
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Assembles transaction data for transferring ownership of a contract
99
+ *
100
+ * For two-step Ownable contracts, this initiates a transfer that must be accepted
101
+ * by the pending owner before the expiration ledger.
102
+ *
103
+ * @param contractAddress The contract address
104
+ * @param newOwner The new owner address
105
+ * @param liveUntilLedger The ledger sequence by which the transfer must be accepted
106
+ * @returns Transaction data ready for execution
107
+ */
108
+ export function assembleTransferOwnershipAction(
109
+ contractAddress: string,
110
+ newOwner: string,
111
+ liveUntilLedger: number
112
+ ): StellarTransactionData {
113
+ logger.info(
114
+ 'assembleTransferOwnershipAction',
115
+ `Assembling transfer_ownership action to ${newOwner} with expiration at ledger ${liveUntilLedger}`
116
+ );
117
+
118
+ // Arguments for transfer_ownership(new_owner: Address, live_until_ledger: u32)
119
+ // Note: args are raw values that will be converted to ScVal by the transaction execution flow
120
+ return {
121
+ contractAddress,
122
+ functionName: 'transfer_ownership',
123
+ args: [newOwner, liveUntilLedger],
124
+ argTypes: ['Address', 'u32'],
125
+ argSchema: undefined,
126
+ transactionOptions: {},
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Assembles transaction data for accepting a pending ownership transfer
132
+ *
133
+ * For two-step Ownable contracts, this completes a pending transfer initiated by
134
+ * the current owner. Must be called by the pending owner before the expiration ledger.
135
+ *
136
+ * @param contractAddress The contract address
137
+ * @returns Transaction data ready for execution
138
+ */
139
+ export function assembleAcceptOwnershipAction(contractAddress: string): StellarTransactionData {
140
+ logger.info(
141
+ 'assembleAcceptOwnershipAction',
142
+ `Assembling accept_ownership action for ${contractAddress}`
143
+ );
144
+
145
+ // accept_ownership() has no arguments - caller must be the pending owner
146
+ return {
147
+ contractAddress,
148
+ functionName: 'accept_ownership',
149
+ args: [],
150
+ argTypes: [],
151
+ argSchema: undefined,
152
+ transactionOptions: {},
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Assembles transaction data for initiating an admin role transfer
158
+ *
159
+ * For two-step AccessControl contracts, this initiates an admin transfer that must
160
+ * be accepted by the pending admin before the expiration ledger.
161
+ *
162
+ * @param contractAddress The contract address
163
+ * @param newAdmin The new admin address
164
+ * @param liveUntilLedger The ledger sequence by which the transfer must be accepted
165
+ * @returns Transaction data ready for execution
166
+ */
167
+ export function assembleTransferAdminRoleAction(
168
+ contractAddress: string,
169
+ newAdmin: string,
170
+ liveUntilLedger: number
171
+ ): StellarTransactionData {
172
+ logger.info(
173
+ 'assembleTransferAdminRoleAction',
174
+ `Assembling transfer_admin_role action to ${newAdmin} with expiration at ledger ${liveUntilLedger}`
175
+ );
176
+
177
+ // Arguments for transfer_admin_role(new_admin: Address, live_until_ledger: u32)
178
+ // Note: args are raw values that will be converted to ScVal by the transaction execution flow
179
+ return {
180
+ contractAddress,
181
+ functionName: 'transfer_admin_role',
182
+ args: [newAdmin, liveUntilLedger],
183
+ argTypes: ['Address', 'u32'],
184
+ argSchema: undefined,
185
+ transactionOptions: {},
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Assembles transaction data for accepting a pending admin transfer
191
+ *
192
+ * For two-step AccessControl contracts, this completes a pending admin transfer
193
+ * initiated by the current admin. Must be called by the pending admin before the
194
+ * expiration ledger.
195
+ *
196
+ * @param contractAddress The contract address
197
+ * @returns Transaction data ready for execution
198
+ */
199
+ export function assembleAcceptAdminTransferAction(contractAddress: string): StellarTransactionData {
200
+ logger.info(
201
+ 'assembleAcceptAdminTransferAction',
202
+ `Assembling accept_admin_transfer action for ${contractAddress}`
203
+ );
204
+
205
+ // accept_admin_transfer() has no arguments - caller must be the pending admin
206
+ return {
207
+ contractAddress,
208
+ functionName: 'accept_admin_transfer',
209
+ args: [],
210
+ argTypes: [],
211
+ argSchema: undefined,
212
+ transactionOptions: {},
213
+ };
214
+ }
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Feature Detection Module
3
+ *
4
+ * Detects access control capabilities of Stellar (Soroban) contracts by analyzing
5
+ * their function interfaces. Supports detection of Ownable and AccessControl patterns
6
+ * from OpenZeppelin Stellar Contracts.
7
+ */
8
+
9
+ import type { AccessControlCapabilities, ContractSchema } from '@openzeppelin/ui-types';
10
+ import { UnsupportedContractFeatures } from '@openzeppelin/ui-types';
11
+
12
+ /**
13
+ * Known OpenZeppelin Ownable function signatures
14
+ */
15
+ const OWNABLE_FUNCTIONS = {
16
+ required: ['get_owner'],
17
+ optional: ['transfer_ownership', 'accept_ownership', 'renounce_ownership'],
18
+ } as const;
19
+
20
+ /**
21
+ * Known OpenZeppelin AccessControl function signatures
22
+ */
23
+ const ACCESS_CONTROL_FUNCTIONS = {
24
+ required: ['has_role', 'grant_role', 'revoke_role'],
25
+ optional: [
26
+ 'get_role_admin',
27
+ 'set_role_admin',
28
+ 'get_admin',
29
+ 'transfer_admin_role',
30
+ 'accept_admin_transfer',
31
+ 'renounce_admin',
32
+ 'renounce_role',
33
+ ],
34
+ } as const;
35
+
36
+ /**
37
+ * Functions that indicate role enumeration support
38
+ */
39
+ const ENUMERATION_FUNCTIONS = ['get_role_member_count', 'get_role_member'] as const;
40
+
41
+ /**
42
+ * Detects access control capabilities of a Stellar contract
43
+ *
44
+ * @param contractSchema The contract schema to analyze
45
+ * @param indexerAvailable Whether an indexer is configured and available
46
+ * @returns Detected capabilities
47
+ */
48
+ export function detectAccessControlCapabilities(
49
+ contractSchema: ContractSchema,
50
+ indexerAvailable = false
51
+ ): AccessControlCapabilities {
52
+ const functionNames = new Set(contractSchema.functions.map((fn) => fn.name));
53
+
54
+ // Detect Ownable
55
+ const hasOwnable = OWNABLE_FUNCTIONS.required.every((fnName) => functionNames.has(fnName));
56
+
57
+ // Detect two-step Ownable (has accept_ownership function)
58
+ const hasTwoStepOwnable = hasOwnable && functionNames.has('accept_ownership');
59
+
60
+ // Detect AccessControl
61
+ const hasAccessControl = ACCESS_CONTROL_FUNCTIONS.required.every((fnName) =>
62
+ functionNames.has(fnName)
63
+ );
64
+
65
+ // Detect two-step admin transfer (has accept_admin_transfer function)
66
+ const hasTwoStepAdmin = hasAccessControl && functionNames.has('accept_admin_transfer');
67
+
68
+ // Detect enumerable roles
69
+ const hasEnumerableRoles = ENUMERATION_FUNCTIONS.every((fnName) => functionNames.has(fnName));
70
+
71
+ // History is only available when indexer is configured
72
+ const supportsHistory = indexerAvailable;
73
+
74
+ // Verify the contract against OZ interfaces
75
+ const verifiedAgainstOZInterfaces = verifyOZInterface(
76
+ functionNames,
77
+ hasOwnable,
78
+ hasAccessControl,
79
+ hasTwoStepOwnable,
80
+ hasTwoStepAdmin
81
+ );
82
+
83
+ // Collect notes about detected capabilities
84
+ const notes: string[] = [];
85
+
86
+ if (hasOwnable) {
87
+ if (hasTwoStepOwnable) {
88
+ notes.push('OpenZeppelin two-step Ownable interface detected (with accept_ownership)');
89
+ } else {
90
+ notes.push('OpenZeppelin Ownable interface detected');
91
+ }
92
+ }
93
+
94
+ if (hasAccessControl) {
95
+ if (hasTwoStepAdmin) {
96
+ notes.push(
97
+ 'OpenZeppelin two-step AccessControl interface detected (with accept_admin_transfer)'
98
+ );
99
+ } else {
100
+ notes.push('OpenZeppelin AccessControl interface detected');
101
+ }
102
+ }
103
+
104
+ if (hasEnumerableRoles) {
105
+ notes.push('Role enumeration supported (get_role_member_count, get_role_member)');
106
+ } else if (hasAccessControl) {
107
+ notes.push('Role enumeration not available - requires event reconstruction');
108
+ }
109
+
110
+ if (!indexerAvailable && (hasOwnable || hasAccessControl)) {
111
+ notes.push('History queries unavailable without indexer configuration');
112
+ }
113
+
114
+ if (!hasOwnable && !hasAccessControl) {
115
+ notes.push('No OpenZeppelin access control interfaces detected');
116
+ }
117
+
118
+ // ── Stellar-specific capability flags ──────────────────────────────
119
+ // Stellar contracts have renounce_ownership but NOT renounce_role (uses revoke_role for self)
120
+ // Stellar has no cancel-admin-transfer or admin-delay-management concepts
121
+ const hasRenounceOwnership = hasOwnable && functionNames.has('renounce_ownership');
122
+ const hasRenounceRole = false; // Stellar uses revokeRole for self-revocation
123
+ const hasCancelAdminTransfer = false; // Not supported on Stellar
124
+ const hasAdminDelayManagement = false; // Not supported on Stellar
125
+
126
+ return {
127
+ hasOwnable,
128
+ hasTwoStepOwnable,
129
+ hasAccessControl,
130
+ hasTwoStepAdmin,
131
+ hasEnumerableRoles,
132
+ supportsHistory,
133
+ verifiedAgainstOZInterfaces,
134
+ notes: notes.length > 0 ? notes : undefined,
135
+ hasRenounceOwnership,
136
+ hasRenounceRole,
137
+ hasCancelAdminTransfer,
138
+ hasAdminDelayManagement,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Verifies that the contract conforms to OpenZeppelin interfaces
144
+ *
145
+ * @param functionNames Set of function names in the contract
146
+ * @param hasOwnable Whether Ownable was detected
147
+ * @param hasAccessControl Whether AccessControl was detected
148
+ * @param hasTwoStepOwnable Whether two-step Ownable was detected
149
+ * @param hasTwoStepAdmin Whether two-step admin was detected
150
+ * @returns True if verified against OZ interfaces
151
+ */
152
+ function verifyOZInterface(
153
+ functionNames: Set<string>,
154
+ hasOwnable: boolean,
155
+ hasAccessControl: boolean,
156
+ hasTwoStepOwnable = false,
157
+ hasTwoStepAdmin = false
158
+ ): boolean {
159
+ // If no OZ patterns detected, not applicable
160
+ if (!hasOwnable && !hasAccessControl) {
161
+ return false;
162
+ }
163
+
164
+ // Verify Ownable optional functions
165
+ // For two-step Ownable, require at least 3 of 4 optional functions
166
+ // For basic Ownable, at least 2 of 3 (excluding accept_ownership)
167
+ if (hasOwnable) {
168
+ const ownableOptionalCount = OWNABLE_FUNCTIONS.optional.filter((fnName) =>
169
+ functionNames.has(fnName)
170
+ ).length;
171
+
172
+ if (hasTwoStepOwnable) {
173
+ // Two-step Ownable should have at least 3 of 4 optional functions
174
+ // (transfer_ownership, accept_ownership, renounce_ownership, and accept_ownership is guaranteed)
175
+ if (ownableOptionalCount < 3) {
176
+ return false;
177
+ }
178
+ } else {
179
+ // Basic Ownable should have at least 2 of 3 optional functions
180
+ if (ownableOptionalCount < 2) {
181
+ return false;
182
+ }
183
+ }
184
+ }
185
+
186
+ // Verify AccessControl optional functions
187
+ // For two-step admin, require at least 5 of 7 optional functions
188
+ // For basic AccessControl, at least 4 of 7 should be present
189
+ if (hasAccessControl) {
190
+ const accessControlOptionalCount = ACCESS_CONTROL_FUNCTIONS.optional.filter((fnName) =>
191
+ functionNames.has(fnName)
192
+ ).length;
193
+
194
+ if (hasTwoStepAdmin) {
195
+ // Two-step admin should have at least 5 of 7 optional functions
196
+ // (transfer_admin_role, accept_admin_transfer guaranteed, plus others)
197
+ if (accessControlOptionalCount < 5) {
198
+ return false;
199
+ }
200
+ } else {
201
+ // Basic AccessControl should have at least 4 of 7 optional functions
202
+ if (accessControlOptionalCount < 4) {
203
+ return false;
204
+ }
205
+ }
206
+ }
207
+
208
+ return true;
209
+ }
210
+
211
+ /**
212
+ * Validates that a contract supports access control operations
213
+ *
214
+ * @param capabilities The detected capabilities
215
+ * @param contractAddress Optional contract address for error context
216
+ * @throws UnsupportedContractFeatures if contract doesn't support required operations
217
+ */
218
+ export function validateAccessControlSupport(
219
+ capabilities: AccessControlCapabilities,
220
+ contractAddress?: string
221
+ ): asserts capabilities is
222
+ | (AccessControlCapabilities & { hasOwnable: true })
223
+ | (AccessControlCapabilities & { hasAccessControl: true }) {
224
+ if (!capabilities.hasOwnable && !capabilities.hasAccessControl) {
225
+ throw new UnsupportedContractFeatures(
226
+ 'Contract does not implement OpenZeppelin Ownable or AccessControl interfaces',
227
+ contractAddress,
228
+ ['Ownable', 'AccessControl']
229
+ );
230
+ }
231
+
232
+ if (!capabilities.verifiedAgainstOZInterfaces) {
233
+ throw new UnsupportedContractFeatures(
234
+ 'Contract interfaces do not conform to OpenZeppelin standards',
235
+ contractAddress
236
+ );
237
+ }
238
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Access Control Module
3
+ *
4
+ * Exports access control functionality for Stellar (Soroban) contracts including
5
+ * capability detection, on-chain data reading, action assembly, validation, indexer client,
6
+ * and the AccessControlService.
7
+ *
8
+ * ## Two-Step Ownable Support
9
+ *
10
+ * This module provides full support for OpenZeppelin's two-step Ownable pattern:
11
+ * - {@link StellarAccessControlService.getOwnership} - Returns ownership state (owned/pending/expired/renounced)
12
+ * - {@link StellarAccessControlService.transferOwnership} - Initiates two-step transfer with expiration
13
+ * - {@link StellarAccessControlService.acceptOwnership} - Accepts pending ownership transfer
14
+ * - {@link getCurrentLedger} - Gets current ledger sequence for expiration calculation
15
+ * - {@link validateExpirationLedger} - Validates expiration ledger before submission
16
+ *
17
+ * ## Two-Step Admin Transfer Support
18
+ *
19
+ * This module provides full support for OpenZeppelin's two-step admin transfer pattern:
20
+ * - {@link StellarAccessControlService.getAdminInfo} - Returns admin state (active/pending/expired/renounced)
21
+ * - {@link StellarAccessControlService.transferAdminRole} - Initiates two-step admin transfer with expiration
22
+ * - {@link StellarAccessControlService.acceptAdminTransfer} - Accepts pending admin transfer
23
+ * - {@link AdminTransferInitiatedEvent} - Pending admin transfer event from indexer
24
+ *
25
+ * ## Action Assembly
26
+ *
27
+ * - {@link assembleGrantRoleAction} - Prepares grant_role transaction
28
+ * - {@link assembleRevokeRoleAction} - Prepares revoke_role transaction
29
+ * - {@link assembleTransferOwnershipAction} - Prepares transfer_ownership transaction with expiration
30
+ * - {@link assembleAcceptOwnershipAction} - Prepares accept_ownership transaction
31
+ * - {@link assembleTransferAdminRoleAction} - Prepares transfer_admin_role transaction with expiration
32
+ * - {@link assembleAcceptAdminTransferAction} - Prepares accept_admin_transfer transaction
33
+ *
34
+ * ## Feature Detection
35
+ *
36
+ * - {@link detectAccessControlCapabilities} - Detects Ownable/AccessControl support
37
+ * - `hasTwoStepOwnable` capability flag indicates two-step ownership transfer support
38
+ * - `hasTwoStepAdmin` capability flag indicates two-step admin transfer support
39
+ *
40
+ * ## Indexer Client
41
+ *
42
+ * - {@link StellarIndexerClient} - Queries historical events and pending transfers
43
+ * - {@link OwnershipTransferStartedEvent} - Pending ownership transfer event from indexer
44
+ * - {@link AdminTransferInitiatedEvent} - Pending admin transfer event from indexer
45
+ *
46
+ * @module access-control
47
+ */
48
+
49
+ export * from './actions';
50
+ export * from './feature-detection';
51
+ export * from './indexer-client';
52
+ export * from './onchain-reader';
53
+ export * from './service';
54
+ export * from './validation';