@portal-hq/web 3.13.2 → 3.14.0-alpha.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 (96) hide show
  1. package/lib/commonjs/index.js +127 -9
  2. package/lib/commonjs/index.test.js +13 -0
  3. package/lib/commonjs/integrations/delegations/index.js +109 -2
  4. package/lib/commonjs/integrations/delegations/index.test.js +171 -0
  5. package/lib/commonjs/integrations/ramps/noah/index.test.js +18 -5
  6. package/lib/commonjs/integrations/trading/index.js +16 -5
  7. package/lib/commonjs/integrations/trading/lifi/index.js +297 -25
  8. package/lib/commonjs/integrations/trading/lifi/lifi.tradeAsset.test.js +360 -0
  9. package/lib/commonjs/integrations/trading/lifi/lifiStatusPoll.js +118 -0
  10. package/lib/commonjs/integrations/trading/lifi/lifiStatusPoll.test.js +66 -0
  11. package/lib/commonjs/integrations/trading/zero-x/index.js +129 -26
  12. package/lib/commonjs/integrations/trading/zero-x/index.test.js +163 -1
  13. package/lib/commonjs/integrations/yield/index.js +18 -4
  14. package/lib/commonjs/integrations/yield/yieldxyz.getValidators.test.js +71 -0
  15. package/lib/commonjs/integrations/yield/yieldxyz.highLevel.test.js +330 -0
  16. package/lib/commonjs/integrations/yield/yieldxyz.js +517 -1
  17. package/lib/commonjs/internal/pollLoop.js +64 -0
  18. package/lib/commonjs/internal/pollLoop.test.js +100 -0
  19. package/lib/commonjs/internal/stripStalePlanningNonce.js +65 -0
  20. package/lib/commonjs/internal/stripStalePlanningNonce.test.js +35 -0
  21. package/lib/commonjs/internal/waitForEvmOrUserOpConfirmation.js +155 -0
  22. package/lib/commonjs/internal/waitForEvmOrUserOpConfirmation.test.js +33 -0
  23. package/lib/commonjs/internal/waitForEvmTxConfirmation.js +104 -0
  24. package/lib/commonjs/internal/waitForSolanaTxConfirmation.js +106 -0
  25. package/lib/commonjs/internal/yieldEvmNetwork.js +60 -0
  26. package/lib/commonjs/mpc/index.js +116 -1
  27. package/lib/commonjs/provider/index.js +17 -0
  28. package/lib/commonjs/shared/trace/index.js +0 -1
  29. package/lib/esm/index.js +127 -9
  30. package/lib/esm/index.test.js +13 -0
  31. package/lib/esm/integrations/delegations/index.js +109 -2
  32. package/lib/esm/integrations/delegations/index.test.js +171 -0
  33. package/lib/esm/integrations/ramps/noah/index.test.js +18 -5
  34. package/lib/esm/integrations/trading/index.js +16 -5
  35. package/lib/esm/integrations/trading/lifi/index.js +292 -25
  36. package/lib/esm/integrations/trading/lifi/lifi.tradeAsset.test.js +332 -0
  37. package/lib/esm/integrations/trading/lifi/lifiStatusPoll.js +113 -0
  38. package/lib/esm/integrations/trading/lifi/lifiStatusPoll.test.js +64 -0
  39. package/lib/esm/integrations/trading/zero-x/index.js +129 -26
  40. package/lib/esm/integrations/trading/zero-x/index.test.js +141 -2
  41. package/lib/esm/integrations/yield/index.js +18 -4
  42. package/lib/esm/integrations/yield/yieldxyz.getValidators.test.js +66 -0
  43. package/lib/esm/integrations/yield/yieldxyz.highLevel.test.js +325 -0
  44. package/lib/esm/integrations/yield/yieldxyz.js +517 -1
  45. package/lib/esm/internal/pollLoop.js +59 -0
  46. package/lib/esm/internal/pollLoop.test.js +98 -0
  47. package/lib/esm/internal/stripStalePlanningNonce.js +61 -0
  48. package/lib/esm/internal/stripStalePlanningNonce.test.js +33 -0
  49. package/lib/esm/internal/waitForEvmOrUserOpConfirmation.js +151 -0
  50. package/lib/esm/internal/waitForEvmOrUserOpConfirmation.test.js +31 -0
  51. package/lib/esm/internal/waitForEvmTxConfirmation.js +100 -0
  52. package/lib/esm/internal/waitForSolanaTxConfirmation.js +102 -0
  53. package/lib/esm/internal/yieldEvmNetwork.js +55 -0
  54. package/lib/esm/mpc/index.js +116 -1
  55. package/lib/esm/provider/index.js +17 -0
  56. package/lib/esm/shared/trace/index.js +0 -1
  57. package/noah-types.d.ts +16 -2
  58. package/package.json +3 -2
  59. package/src/index.test.ts +15 -0
  60. package/src/index.ts +203 -14
  61. package/src/integrations/delegations/index.test.ts +251 -0
  62. package/src/integrations/delegations/index.ts +202 -4
  63. package/src/integrations/ramps/noah/index.test.ts +18 -5
  64. package/src/integrations/trading/index.ts +10 -7
  65. package/src/integrations/trading/lifi/index.ts +388 -28
  66. package/src/integrations/trading/lifi/lifi.tradeAsset.test.ts +436 -0
  67. package/src/integrations/trading/lifi/lifiStatusPoll.test.ts +74 -0
  68. package/src/integrations/trading/lifi/lifiStatusPoll.ts +158 -0
  69. package/src/integrations/trading/zero-x/index.test.ts +297 -1
  70. package/src/integrations/trading/zero-x/index.ts +181 -27
  71. package/src/integrations/yield/index.ts +24 -4
  72. package/src/integrations/yield/yieldxyz.getValidators.test.ts +70 -0
  73. package/src/integrations/yield/yieldxyz.highLevel.test.ts +403 -0
  74. package/src/integrations/yield/yieldxyz.ts +740 -8
  75. package/src/internal/pollLoop.test.ts +109 -0
  76. package/src/internal/pollLoop.ts +87 -0
  77. package/src/internal/stripStalePlanningNonce.test.ts +38 -0
  78. package/src/internal/stripStalePlanningNonce.ts +66 -0
  79. package/src/internal/waitForEvmOrUserOpConfirmation.test.ts +31 -0
  80. package/src/internal/waitForEvmOrUserOpConfirmation.ts +194 -0
  81. package/src/internal/waitForEvmTxConfirmation.ts +155 -0
  82. package/src/internal/waitForSolanaTxConfirmation.ts +135 -0
  83. package/src/internal/yieldEvmNetwork.ts +57 -0
  84. package/src/mpc/index.ts +142 -1
  85. package/src/provider/index.ts +25 -0
  86. package/src/shared/trace/index.ts +0 -1
  87. package/src/shared/types/README.md +6 -0
  88. package/src/shared/types/api.ts +12 -1
  89. package/src/shared/types/common.ts +332 -20
  90. package/src/shared/types/delegations.ts +10 -0
  91. package/src/shared/types/index.ts +1 -0
  92. package/src/shared/types/lifi.ts +82 -0
  93. package/src/shared/types/noah.ts +124 -33
  94. package/src/shared/types/yieldxyz.ts +186 -0
  95. package/src/shared/types/zero-x.ts +66 -0
  96. package/types.d.ts +6 -0
@@ -0,0 +1,135 @@
1
+ import { sdkLogger } from '../logger'
2
+
3
+ const LOG_PREFIX = '[Portal.waitForConfirmation]'
4
+
5
+ export type SolanaConfirmationCommitment =
6
+ | 'processed'
7
+ | 'confirmed'
8
+ | 'finalized'
9
+
10
+ export type SolanaRequestFn = (
11
+ method: string,
12
+ params: unknown[],
13
+ network: string,
14
+ ) => Promise<unknown>
15
+
16
+ export interface WaitForSolanaTxConfirmationOptions {
17
+ pollIntervalMs: number
18
+ timeoutMs: number
19
+ commitment: SolanaConfirmationCommitment
20
+ }
21
+
22
+ const COMMITMENT_ORDER: Record<SolanaConfirmationCommitment, number> = {
23
+ processed: 0,
24
+ confirmed: 1,
25
+ finalized: 2,
26
+ }
27
+
28
+ function sleep(ms: number): Promise<void> {
29
+ return new Promise((resolve) => setTimeout(resolve, ms))
30
+ }
31
+
32
+ function statusMeetsCommitment(
33
+ confirmationStatus: string | undefined,
34
+ required: SolanaConfirmationCommitment,
35
+ ): boolean {
36
+ if (!confirmationStatus) return false
37
+ const level = COMMITMENT_ORDER[confirmationStatus as SolanaConfirmationCommitment]
38
+ const need = COMMITMENT_ORDER[required]
39
+ if (level === undefined || need === undefined) return false
40
+ return level >= need
41
+ }
42
+
43
+ interface SignatureStatusRow {
44
+ err?: unknown
45
+ confirmationStatus?: string
46
+ }
47
+
48
+ function parseSignatureStatusesResult(raw: unknown): SignatureStatusRow | null {
49
+ if (raw == null || typeof raw !== 'object') return null
50
+ const outer = raw as { value?: unknown; result?: unknown }
51
+ const arr = outer.value ?? outer.result
52
+ if (!Array.isArray(arr) || arr.length === 0) return null
53
+ const first = arr[0]
54
+ if (first == null || typeof first !== 'object') return null
55
+ return first as SignatureStatusRow
56
+ }
57
+
58
+ /**
59
+ * Polls Solana `getSignatureStatuses` via {@link request} (routed through the
60
+ * iframe RPC proxy) until the signature reaches the requested commitment,
61
+ * fails on-chain, or times out.
62
+ */
63
+ export async function waitForSolanaTxConfirmation(
64
+ signature: string,
65
+ network: string,
66
+ request: SolanaRequestFn,
67
+ options: WaitForSolanaTxConfirmationOptions,
68
+ ): Promise<boolean> {
69
+ const { pollIntervalMs, timeoutMs, commitment } = options
70
+
71
+ sdkLogger.debug(`${LOG_PREFIX} waiting for Solana confirmation`, {
72
+ signature,
73
+ network,
74
+ pollIntervalMs,
75
+ timeoutMs,
76
+ commitment,
77
+ })
78
+
79
+ const deadline = Date.now() + timeoutMs
80
+
81
+ while (Date.now() < deadline) {
82
+ try {
83
+ const raw = await request(
84
+ 'getSignatureStatuses',
85
+ [[signature], { searchTransactionHistory: true }],
86
+ network,
87
+ )
88
+
89
+ const rpcResponse = raw as { result?: unknown; error?: unknown }
90
+ if (rpcResponse.error) {
91
+ throw new Error(
92
+ `${LOG_PREFIX} RPC error for getSignatureStatuses: ${JSON.stringify(rpcResponse.error)}`,
93
+ )
94
+ }
95
+
96
+ const statusRow = parseSignatureStatusesResult(
97
+ rpcResponse.result ?? raw,
98
+ )
99
+ if (statusRow != null) {
100
+ if (statusRow.err != null) {
101
+ sdkLogger.debug(`${LOG_PREFIX} Solana tx error on-chain`, {
102
+ signature,
103
+ network,
104
+ err: statusRow.err,
105
+ })
106
+ return false
107
+ }
108
+ if (statusMeetsCommitment(statusRow.confirmationStatus, commitment)) {
109
+ sdkLogger.debug(`${LOG_PREFIX} Solana commitment met`, {
110
+ signature,
111
+ network,
112
+ confirmationStatus: statusRow.confirmationStatus,
113
+ commitment,
114
+ })
115
+ return true
116
+ }
117
+ }
118
+ } catch (error) {
119
+ sdkLogger.warn(`${LOG_PREFIX} Solana poll transient error`, {
120
+ signature,
121
+ network,
122
+ error: error instanceof Error ? error.message : String(error),
123
+ })
124
+ }
125
+
126
+ const remaining = deadline - Date.now()
127
+ if (remaining <= 0) break
128
+ await sleep(Math.min(pollIntervalMs, Math.max(0, remaining)))
129
+ }
130
+
131
+ sdkLogger.warn(
132
+ `${LOG_PREFIX} timeout after ${timeoutMs}ms waiting for Solana confirmation on ${signature} (${network}). Returning false.`,
133
+ )
134
+ return false
135
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Yield / Portal-style EVM network slugs → EIP-155 CAIP-2.
3
+ */
4
+
5
+ const YIELD_EVM_SLUG_TO_CAIP2: Record<string, string> = {
6
+ // Common friendly names (subset of typical integrations)
7
+ ethereum: 'eip155:1',
8
+ eth: 'eip155:1',
9
+ mainnet: 'eip155:1',
10
+ polygon: 'eip155:137',
11
+ matic: 'eip155:137',
12
+ arbitrum: 'eip155:42161',
13
+ optimism: 'eip155:10',
14
+ base: 'eip155:8453',
15
+ blast: 'eip155:81457',
16
+ scroll: 'eip155:534352',
17
+ mantle: 'eip155:5000',
18
+ mode: 'eip155:34443',
19
+ opbnb: 'eip155:204',
20
+ // Testnets and alternate slugs (e.g. *-sepolia) mapped to EIP-155 CAIP-2
21
+ 'ethereum-sepolia': 'eip155:11155111',
22
+ 'ethereum-goerli': 'eip155:5',
23
+ 'ethereum-holesky': 'eip155:17000',
24
+ 'ethereum-hoodi': 'eip155:560048',
25
+ binance: 'eip155:56',
26
+ bsc: 'eip155:56',
27
+ 'avalanche-c': 'eip155:43114',
28
+ 'avalanche-c-atomic': 'eip155:43114',
29
+ gnosis: 'eip155:100',
30
+ zksync: 'eip155:324',
31
+ linea: 'eip155:59144',
32
+ celo: 'eip155:42220',
33
+ fantom: 'eip155:250',
34
+ harmony: 'eip155:1666600000',
35
+ moonriver: 'eip155:1285',
36
+ okc: 'eip155:66',
37
+ viction: 'eip155:88',
38
+ core: 'eip155:1116',
39
+ cronos: 'eip155:25',
40
+ evmos: 'eip155:9001',
41
+ unichain: 'eip155:130',
42
+ sonic: 'eip155:146',
43
+ }
44
+
45
+ /** Resolve a Yield transaction `network` string to EIP-155 CAIP-2 when known. */
46
+ export function resolveYieldNetworkToCaip2(
47
+ network: string,
48
+ ): string | undefined {
49
+ if (typeof network !== 'string' || network === '') return undefined
50
+ if (network.startsWith('eip155:')) return network
51
+ if (network.toLowerCase().startsWith('solana')) return undefined
52
+ return YIELD_EVM_SLUG_TO_CAIP2[network.toLowerCase()]
53
+ }
54
+
55
+ export function isYieldEvmNetwork(network: string): boolean {
56
+ return resolveYieldNetworkToCaip2(network) !== undefined
57
+ }
package/src/mpc/index.ts CHANGED
@@ -41,6 +41,10 @@ import type {
41
41
  FundParams,
42
42
  FundResponse,
43
43
  } from '../../types'
44
+ import type {
45
+ GetTransactionHistoryParams,
46
+ GetTransactionHistoryResponse,
47
+ } from '../shared/types'
44
48
  import {
45
49
  YieldXyzEnterRequest,
46
50
  YieldXyzEnterYieldResponse,
@@ -57,6 +61,9 @@ import {
57
61
  YieldXyzManageYieldResponse,
58
62
  YieldXyzTrackTransactionRequest,
59
63
  YieldXyzTrackTransactionResponse,
64
+ YieldXyzGetYieldDefaultsRequest,
65
+ YieldXyzGetYieldDefaultsResponse,
66
+ YieldXyzGetYieldValidatorsResponse,
60
67
  LifiQuoteRequest,
61
68
  LifiQuoteResponse,
62
69
  LifiRoutesRequest,
@@ -118,6 +125,8 @@ import {
118
125
  TransferFromRequest,
119
126
  TransferFromResponse,
120
127
  RawSignOptions,
128
+ RpcProxyRequest,
129
+ RpcProxyResponse,
121
130
  } from '../shared/types'
122
131
  import {
123
132
  ScreenAddressApiResponse,
@@ -125,7 +134,7 @@ import {
125
134
  } from '../../hypernative'
126
135
  import { generateTraceId } from '../shared/trace'
127
136
 
128
- const WEB_SDK_VERSION = '3.13.2'
137
+ const WEB_SDK_VERSION = '3.14.0-alpha.0'
129
138
 
130
139
  class Mpc {
131
140
  public iframe?: HTMLIFrameElement
@@ -452,12 +461,25 @@ class Mpc {
452
461
  })
453
462
  }
454
463
 
464
+ /**
465
+ * @deprecated This method is deprecated and will be removed in a future version.
466
+ * Please use `getTransactionHistory()` instead, which uses the new Portal v3 API
467
+ * endpoint and returns the unified transaction format across all chains.
468
+ */
455
469
  public async getTransactions(
456
470
  chainId: string,
457
471
  limit?: number,
458
472
  offset?: number,
459
473
  order?: GetTransactionsOrder,
460
474
  ): Promise<Transaction[]> {
475
+ // Log deprecation warning
476
+ sdkLogger.warn(
477
+ '[DEPRECATED] getTransactions() is deprecated and will be removed in a future version. ' +
478
+ 'Please use getTransactionHistory() instead, which provides improved type safety with ' +
479
+ 'discriminated unions for regular transactions and UserOperations, and proper polymorphic ' +
480
+ 'response types for Solana vs unified formats.'
481
+ )
482
+
461
483
  return this.handleRequestToIframeAndPost({
462
484
  methodMessage: 'portal:getTransactions',
463
485
  errorMessage: 'portal:getTransactionsError',
@@ -471,6 +493,32 @@ class Mpc {
471
493
  })
472
494
  }
473
495
 
496
+ /**
497
+ * Retrieves transaction history for the client's wallet on the specified chain.
498
+ *
499
+ * This method uses the new Portal v3 API endpoint and returns the unified
500
+ * transaction format. Supports EVM (EIP-155), Solana, Bitcoin, Tron, and Stellar chains.
501
+ *
502
+ * @param params - Request parameters
503
+ * @param params.chainId - Chain ID in CAIP-2 format (e.g., 'eip155:1', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp')
504
+ * @param params.limit - Maximum number of transactions to return (default: 50)
505
+ * @param params.offset - Number of transactions to skip (default: 0)
506
+ * @param params.order - Sort order ('asc' or 'desc')
507
+ * @param params.address - Override wallet address (EVM only)
508
+ * @param params.userOperations - Filter for ERC-4337 UserOperations (EVM only)
509
+ * @returns Promise resolving to transaction history response
510
+ */
511
+ public async getTransactionHistory(
512
+ params: GetTransactionHistoryParams,
513
+ ): Promise<GetTransactionHistoryResponse> {
514
+ return this.handleRequestToIframeAndPost({
515
+ methodMessage: 'portal:getTransactionHistory',
516
+ errorMessage: 'portal:getTransactionHistoryError',
517
+ resultMessage: 'portal:getTransactionHistoryResult',
518
+ data: params,
519
+ })
520
+ }
521
+
474
522
  public async setBackupStatus(
475
523
  status: string,
476
524
  backupIds: string[],
@@ -640,6 +688,28 @@ class Mpc {
640
688
  })
641
689
  }
642
690
 
691
+ public async getYieldXyzDefaults(
692
+ data?: YieldXyzGetYieldDefaultsRequest,
693
+ ): Promise<YieldXyzGetYieldDefaultsResponse> {
694
+ return this.handleRequestToIframeAndPost({
695
+ methodMessage: 'portal:yieldxyz:getDefaults',
696
+ errorMessage: 'portal:yieldxyz:getDefaultsError',
697
+ resultMessage: 'portal:yieldxyz:getDefaultsResult',
698
+ data: data ?? {},
699
+ })
700
+ }
701
+
702
+ public async getYieldXyzValidators(
703
+ yieldId: string,
704
+ ): Promise<YieldXyzGetYieldValidatorsResponse> {
705
+ return this.handleRequestToIframeAndPost({
706
+ methodMessage: 'portal:yieldxyz:getValidators',
707
+ errorMessage: 'portal:yieldxyz:getValidatorsError',
708
+ resultMessage: 'portal:yieldxyz:getValidatorsResult',
709
+ data: yieldId,
710
+ })
711
+ }
712
+
643
713
  public async getLifiRoutes(
644
714
  data: LifiRoutesRequest,
645
715
  ): Promise<LifiRoutesResponse> {
@@ -1122,6 +1192,8 @@ class Mpc {
1122
1192
  mpcVersion: this.portal.mpcVersion,
1123
1193
  featureFlags: this.portal.featureFlags,
1124
1194
  logLevel: this.portal.getLogLevel(),
1195
+ rpcConfig: this.portal.rpcConfig,
1196
+ iframeRpcConfig: this.portal.iframeRpcConfig,
1125
1197
  }
1126
1198
 
1127
1199
  const message = {
@@ -1210,6 +1282,75 @@ class Mpc {
1210
1282
  })
1211
1283
  }
1212
1284
 
1285
+ public async rpcRequest(
1286
+ data: Omit<RpcProxyRequest, 'requestId'>,
1287
+ options?: { timeoutMs?: number; traceId?: string },
1288
+ ): Promise<RpcProxyResponse> {
1289
+ const { timeoutMs = 30_000, traceId } = options ?? {}
1290
+ const requestId =
1291
+ crypto.randomUUID?.() ?? `${Date.now()}-${Math.random()}`
1292
+ const resolvedTraceId = traceId ?? generateTraceId()
1293
+
1294
+ sdkLogger.debug('[Portal] rpcRequest', {
1295
+ requestId,
1296
+ method: data.method,
1297
+ chainId: data.chainId,
1298
+ traceId: resolvedTraceId,
1299
+ timeoutMs,
1300
+ })
1301
+
1302
+ return new Promise((resolve, reject) => {
1303
+ let timeoutId: ReturnType<typeof setTimeout> | undefined
1304
+
1305
+ const cleanup = () => {
1306
+ window.removeEventListener('message', handleResponse)
1307
+ if (timeoutId !== undefined) {
1308
+ clearTimeout(timeoutId)
1309
+ }
1310
+ }
1311
+
1312
+ const handleResponse = (event: MessageEvent) => {
1313
+ const { origin } = event
1314
+ if (origin !== this.getOrigin()) return
1315
+
1316
+ const { type, data: result } = event.data
1317
+ if (
1318
+ type === 'portal:rpc:requestResult' &&
1319
+ result?.requestId === requestId
1320
+ ) {
1321
+ cleanup()
1322
+ resolve(result as RpcProxyResponse)
1323
+ } else if (
1324
+ type === 'portal:rpc:requestError' &&
1325
+ result?.requestId === requestId
1326
+ ) {
1327
+ cleanup()
1328
+ reject(new Error(result.message ?? 'RPC proxy error'))
1329
+ }
1330
+ }
1331
+
1332
+ timeoutId = setTimeout(() => {
1333
+ cleanup()
1334
+ const msg = `RPC request ${requestId} (${data.method}) timed out after ${timeoutMs}ms`
1335
+ sdkLogger.error('[Portal] rpcRequest timeout', {
1336
+ requestId,
1337
+ method: data.method,
1338
+ chainId: data.chainId,
1339
+ timeoutMs,
1340
+ })
1341
+ reject(new Error(msg))
1342
+ }, timeoutMs)
1343
+
1344
+ window.addEventListener('message', handleResponse)
1345
+
1346
+ this.postMessage({
1347
+ type: 'portal:rpc:request',
1348
+ data: { ...data, requestId },
1349
+ traceId: resolvedTraceId,
1350
+ })
1351
+ })
1352
+ }
1353
+
1213
1354
  private postMessage(event: { type: string; data: any; traceId?: string }) {
1214
1355
  this.iframe?.contentWindow?.postMessage(event, this.getOrigin())
1215
1356
  }
@@ -157,6 +157,12 @@ const signerMethods = [
157
157
  RequestMethod.sol_signTransaction,
158
158
  ]
159
159
 
160
+ const iframeProxiedMethodStrings: string[] = [
161
+ 'eth_getTransactionReceipt',
162
+ 'eth_getUserOperationReceipt',
163
+ 'getSignatureStatuses',
164
+ ]
165
+
160
166
  class Provider {
161
167
  public events: Record<string, RegisteredEventHandler[]>
162
168
 
@@ -419,6 +425,25 @@ class Provider {
419
425
  params,
420
426
  traceId,
421
427
  }: RequestArguments): Promise<any> {
428
+ if (iframeProxiedMethodStrings.includes(method)) {
429
+ sdkLogger.info(
430
+ `[PortalProvider] routing ${method} through iframe (chainId=${String(chainId)})`,
431
+ { traceId },
432
+ )
433
+ return this.portal.mpc.rpcRequest(
434
+ {
435
+ method,
436
+ params: Array.isArray(params)
437
+ ? params
438
+ : params != null
439
+ ? [params]
440
+ : [],
441
+ chainId: chainId as string,
442
+ },
443
+ { traceId },
444
+ )
445
+ }
446
+
422
447
  const requestBody = {
423
448
  body: JSON.stringify({
424
449
  jsonrpc: '2.0',
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * Request tracing for Portal API calls.
3
- * Aligns with portal-react-native X-Portal-Trace-Id / traceId behavior.
4
3
  *
5
4
  * Propagation in the Web SDK:
6
5
  * - Browser: High-level methods (backup, recover, sendAsset, provider.request) accept optional
@@ -0,0 +1,6 @@
1
+ # Shared types (canonical source)
2
+
3
+ Type definitions in this folder are the single source of truth for Web and related packages.
4
+
5
+ - **Browser (`packages/browser`)** — run `yarn shared:symlink` (dev) or `yarn shared:copy` (CI/publish) from the browser package so `src/shared` mirrors `packages/shared/src`.
6
+ - **Do not** maintain divergent copies of these files under `packages/browser/src/shared/types`; edit here and re-sync the browser package.
@@ -2,7 +2,7 @@
2
2
  * API response types shared between browser and iframe packages
3
3
  */
4
4
 
5
- import type { FeatureFlags, GDriveConfig, PasskeyConfig } from './common'
5
+ import type { FeatureFlags, GDriveConfig, PasskeyConfig, RpcConfig } from './common'
6
6
 
7
7
  // Asset response types
8
8
  export interface GetAssetsResponse {
@@ -147,6 +147,17 @@ export interface IframeConfigurationOptions {
147
147
  /** When set to 'none', the iframe SDK will not log to the console. Passed from parent via portal:configure. */
148
148
  logLevel?: IframeLogLevel
149
149
 
150
+ /** RPC endpoint map (CAIP-2 chainId → URL). Passed from parent so the iframe can proxy RPC calls. */
151
+ rpcConfig?: RpcConfig
152
+
153
+ /**
154
+ * Optional RPC map used only for iframe-proxied JSON-RPC (receipt polling, Solana status, etc.).
155
+ * When set, the iframe `fetch`es these URLs instead of {@link rpcConfig}. The parent still uses
156
+ * `rpcConfig` for signing (`mpc.sign` / `getRpcUrl`). Use in local dev when the gateway URL must
157
+ * match presignature but the iframe origin is blocked by CORS (e.g. same-origin or local proxy).
158
+ */
159
+ iframeRpcConfig?: RpcConfig
160
+
150
161
  // One of these three is required for authentication
151
162
  apiKey?: string
152
163
  authToken?: string