@circle-fin/app-kit 1.11.0 → 1.12.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/CHANGELOG.md +39 -0
- package/README.md +56 -25
- package/bridge.cjs +227 -30
- package/bridge.d.cts +879 -1
- package/bridge.d.mts +879 -1
- package/bridge.d.ts +879 -1
- package/bridge.mjs +227 -30
- package/chains.cjs +106 -1
- package/chains.d.cts +96 -2
- package/chains.d.mts +96 -2
- package/chains.d.ts +96 -2
- package/chains.mjs +105 -2
- package/context.d.cts +879 -1
- package/context.d.mts +879 -1
- package/context.d.ts +879 -1
- package/earn.cjs +1595 -79
- package/earn.d.cts +879 -1
- package/earn.d.mts +879 -1
- package/earn.d.ts +879 -1
- package/earn.mjs +1595 -79
- package/estimateBridge.cjs +227 -30
- package/estimateBridge.d.cts +879 -1
- package/estimateBridge.d.mts +879 -1
- package/estimateBridge.d.ts +879 -1
- package/estimateBridge.mjs +227 -30
- package/estimateSwap.cjs +135 -12
- package/estimateSwap.d.cts +878 -1
- package/estimateSwap.d.mts +878 -1
- package/estimateSwap.d.ts +878 -1
- package/estimateSwap.mjs +135 -12
- package/index.cjs +2063 -245
- package/index.d.cts +2309 -1653
- package/index.d.mts +2309 -1653
- package/index.d.ts +2309 -1653
- package/index.mjs +2062 -246
- package/package.json +6 -6
- package/swap.cjs +135 -12
- package/swap.d.cts +878 -1
- package/swap.d.mts +878 -1
- package/swap.d.ts +878 -1
- package/swap.mjs +135 -12
- package/unifiedBalance.cjs +316 -107
- package/unifiedBalance.d.cts +2 -0
- package/unifiedBalance.d.mts +2 -0
- package/unifiedBalance.d.ts +2 -0
- package/unifiedBalance.mjs +316 -107
package/unifiedBalance.mjs
CHANGED
|
@@ -658,11 +658,6 @@ class KitError extends Error {
|
|
|
658
658
|
name: 'INPUT_UNSUPPORTED_TOKEN',
|
|
659
659
|
type: 'INPUT'
|
|
660
660
|
},
|
|
661
|
-
/** Action not supported by this adapter / ecosystem */ UNSUPPORTED_ACTION: {
|
|
662
|
-
code: 1008,
|
|
663
|
-
name: 'INPUT_UNSUPPORTED_ACTION',
|
|
664
|
-
type: 'INPUT'
|
|
665
|
-
},
|
|
666
661
|
/** No route satisfies the slippage or minimum-output constraint */ SLIPPAGE_CONSTRAINT_NOT_MET: {
|
|
667
662
|
code: 1009,
|
|
668
663
|
name: 'INPUT_SLIPPAGE_CONSTRAINT_NOT_MET',
|
|
@@ -2319,6 +2314,8 @@ class KitError extends Error {
|
|
|
2319
2314
|
Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
2320
2315
|
Blockchain["XDC"] = "XDC";
|
|
2321
2316
|
Blockchain["XDC_Apothem"] = "XDC_Apothem";
|
|
2317
|
+
Blockchain["X_Layer"] = "X_Layer";
|
|
2318
|
+
Blockchain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
2322
2319
|
Blockchain["ZKSync_Era"] = "ZKSync_Era";
|
|
2323
2320
|
Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
|
|
2324
2321
|
})(Blockchain || (Blockchain = {}));
|
|
@@ -2372,6 +2369,7 @@ var BridgeChain;
|
|
|
2372
2369
|
BridgeChain["Unichain"] = "Unichain";
|
|
2373
2370
|
BridgeChain["World_Chain"] = "World_Chain";
|
|
2374
2371
|
BridgeChain["XDC"] = "XDC";
|
|
2372
|
+
BridgeChain["X_Layer"] = "X_Layer";
|
|
2375
2373
|
// Testnet chains with CCTPv2 support
|
|
2376
2374
|
BridgeChain["Arc_Testnet"] = "Arc_Testnet";
|
|
2377
2375
|
BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
@@ -2397,6 +2395,7 @@ var BridgeChain;
|
|
|
2397
2395
|
BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
2398
2396
|
BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
2399
2397
|
BridgeChain["XDC_Apothem"] = "XDC_Apothem";
|
|
2398
|
+
BridgeChain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
2400
2399
|
})(BridgeChain || (BridgeChain = {}));
|
|
2401
2400
|
var UnifiedBalanceChain;
|
|
2402
2401
|
(function(UnifiedBalanceChain) {
|
|
@@ -4944,7 +4943,8 @@ var EarnChain;
|
|
|
4944
4943
|
isTestnet: true,
|
|
4945
4944
|
explorerUrl: 'https://amoy.polygonscan.com/tx/{hash}',
|
|
4946
4945
|
rpcEndpoints: [
|
|
4947
|
-
'https://
|
|
4946
|
+
'https://polygon-amoy-bor-rpc.publicnode.com',
|
|
4947
|
+
'https://polygon-amoy.drpc.org'
|
|
4948
4948
|
],
|
|
4949
4949
|
eurcAddress: null,
|
|
4950
4950
|
usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -5809,6 +5809,104 @@ var EarnChain;
|
|
|
5809
5809
|
}
|
|
5810
5810
|
});
|
|
5811
5811
|
|
|
5812
|
+
/**
|
|
5813
|
+
* X Layer Mainnet chain definition
|
|
5814
|
+
* @remarks
|
|
5815
|
+
* This represents the official production network for the X Layer blockchain.
|
|
5816
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
5817
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
5818
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
5819
|
+
*/ const XLayer = defineChain({
|
|
5820
|
+
type: 'evm',
|
|
5821
|
+
chain: Blockchain.X_Layer,
|
|
5822
|
+
name: 'X Layer',
|
|
5823
|
+
title: 'X Layer Mainnet',
|
|
5824
|
+
nativeCurrency: {
|
|
5825
|
+
name: 'OKB',
|
|
5826
|
+
symbol: 'OKB',
|
|
5827
|
+
decimals: 18
|
|
5828
|
+
},
|
|
5829
|
+
chainId: 196,
|
|
5830
|
+
isTestnet: false,
|
|
5831
|
+
explorerUrl: 'https://www.oklink.com/xlayer/tx/{hash}',
|
|
5832
|
+
rpcEndpoints: [
|
|
5833
|
+
'https://xlayerrpc.okx.com'
|
|
5834
|
+
],
|
|
5835
|
+
eurcAddress: null,
|
|
5836
|
+
usdcAddress: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
5837
|
+
usdtAddress: null,
|
|
5838
|
+
cctp: {
|
|
5839
|
+
domain: 37,
|
|
5840
|
+
contracts: {
|
|
5841
|
+
v2: {
|
|
5842
|
+
type: 'split',
|
|
5843
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5844
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5845
|
+
confirmations: 65,
|
|
5846
|
+
fastConfirmations: 1
|
|
5847
|
+
}
|
|
5848
|
+
},
|
|
5849
|
+
forwarderSupported: {
|
|
5850
|
+
source: false,
|
|
5851
|
+
destination: false
|
|
5852
|
+
}
|
|
5853
|
+
},
|
|
5854
|
+
kitContracts: {
|
|
5855
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
5856
|
+
}
|
|
5857
|
+
});
|
|
5858
|
+
|
|
5859
|
+
/**
|
|
5860
|
+
* X Layer Testnet chain definition
|
|
5861
|
+
* @remarks
|
|
5862
|
+
* This represents the official test network for the X Layer blockchain.
|
|
5863
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
5864
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
5865
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
5866
|
+
*/ const XLayerTestnet = defineChain({
|
|
5867
|
+
type: 'evm',
|
|
5868
|
+
chain: Blockchain.X_Layer_Testnet,
|
|
5869
|
+
name: 'X Layer Testnet',
|
|
5870
|
+
title: 'X Layer Testnet',
|
|
5871
|
+
nativeCurrency: {
|
|
5872
|
+
name: 'OKB',
|
|
5873
|
+
symbol: 'OKB',
|
|
5874
|
+
decimals: 18
|
|
5875
|
+
},
|
|
5876
|
+
chainId: 1952,
|
|
5877
|
+
isTestnet: true,
|
|
5878
|
+
// Deliberately not oklink.com (used for mainnet): viem's bundled OKLink
|
|
5879
|
+
// testnet URL targets the deprecated pre-rebrand chain ID 195, not this
|
|
5880
|
+
// chain's ID (1952). Verified against the internal chain-expansion-scripts
|
|
5881
|
+
// config (`v2config.sandbox.yml`) — do not "normalize" this to match mainnet.
|
|
5882
|
+
explorerUrl: 'https://web3.okx.com/explorer/x-layer-testnet/tx/{hash}',
|
|
5883
|
+
rpcEndpoints: [
|
|
5884
|
+
'https://testrpc.xlayer.tech'
|
|
5885
|
+
],
|
|
5886
|
+
eurcAddress: null,
|
|
5887
|
+
usdcAddress: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
5888
|
+
usdtAddress: null,
|
|
5889
|
+
cctp: {
|
|
5890
|
+
domain: 37,
|
|
5891
|
+
contracts: {
|
|
5892
|
+
v2: {
|
|
5893
|
+
type: 'split',
|
|
5894
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5895
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5896
|
+
confirmations: 65,
|
|
5897
|
+
fastConfirmations: 1
|
|
5898
|
+
}
|
|
5899
|
+
},
|
|
5900
|
+
forwarderSupported: {
|
|
5901
|
+
source: false,
|
|
5902
|
+
destination: false
|
|
5903
|
+
}
|
|
5904
|
+
},
|
|
5905
|
+
kitContracts: {
|
|
5906
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET
|
|
5907
|
+
}
|
|
5908
|
+
});
|
|
5909
|
+
|
|
5812
5910
|
/**
|
|
5813
5911
|
* ZKSync Era Mainnet chain definition
|
|
5814
5912
|
* @remarks
|
|
@@ -5928,6 +6026,8 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
5928
6026
|
WorldChainSepolia: WorldChainSepolia,
|
|
5929
6027
|
XDC: XDC,
|
|
5930
6028
|
XDCApothem: XDCApothem,
|
|
6029
|
+
XLayer: XLayer,
|
|
6030
|
+
XLayerTestnet: XLayerTestnet,
|
|
5931
6031
|
ZKSyncEra: ZKSyncEra,
|
|
5932
6032
|
ZKSyncEraSepolia: ZKSyncEraSepolia
|
|
5933
6033
|
});
|
|
@@ -7880,6 +7980,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
7880
7980
|
[Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
7881
7981
|
[Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
|
|
7882
7982
|
[Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
7983
|
+
[Blockchain.X_Layer]: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
7883
7984
|
[Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
|
|
7884
7985
|
// =========================================================================
|
|
7885
7986
|
// Testnets (alphabetically sorted)
|
|
@@ -7914,6 +8015,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
7914
8015
|
[Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
|
|
7915
8016
|
[Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
|
|
7916
8017
|
[Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
|
|
8018
|
+
[Blockchain.X_Layer_Testnet]: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
7917
8019
|
[Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853'
|
|
7918
8020
|
}
|
|
7919
8021
|
};
|
|
@@ -8779,7 +8881,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
8779
8881
|
}
|
|
8780
8882
|
|
|
8781
8883
|
var name = "@circle-fin/unified-balance-kit";
|
|
8782
|
-
var version = "1.
|
|
8884
|
+
var version = "1.4.0";
|
|
8783
8885
|
var pkg = {
|
|
8784
8886
|
name: name,
|
|
8785
8887
|
version: version};
|
|
@@ -12060,72 +12162,55 @@ function evmSigningData(burnIntent) {
|
|
|
12060
12162
|
* `0xef0100` followed by the 20-byte delegate address (23 bytes total).
|
|
12061
12163
|
* The underlying secp256k1 key still produces `ecrecover`-verifiable
|
|
12062
12164
|
* signatures, so for Gateway's purposes a 7702-delegated address is
|
|
12063
|
-
* an EOA, not
|
|
12165
|
+
* an EOA, not a contract signer.
|
|
12064
12166
|
*
|
|
12065
12167
|
* Spec: https://eips.ethereum.org/EIPS/eip-7702
|
|
12066
12168
|
*/ const EIP_7702_DELEGATION_PREFIX = '0xef0100';
|
|
12067
12169
|
/**
|
|
12068
|
-
*
|
|
12170
|
+
* Determine whether `address` on `chain` signs as a contract (ERC-1271)
|
|
12171
|
+
* rather than as an EOA.
|
|
12069
12172
|
*
|
|
12070
|
-
* Gateway
|
|
12071
|
-
* `
|
|
12072
|
-
*
|
|
12073
|
-
* ERC-
|
|
12074
|
-
*
|
|
12075
|
-
* against Gateway's chainId-less domain with an opaque
|
|
12076
|
-
* `invalid integer value <nil>/<nil> for type uint256` error.
|
|
12173
|
+
* Gateway validates burn-intent signatures two ways: a static `ecrecover`
|
|
12174
|
+
* check for EOAs, and — for requests that carry `contractSigner: true` —
|
|
12175
|
+
* an offchain `isValidSignature` simulation against the signing contract
|
|
12176
|
+
* (ERC-1271). Gateway does not infer which one to use, so the caller must
|
|
12177
|
+
* declare it. This detects the contract case from on-chain bytecode.
|
|
12077
12178
|
*
|
|
12078
|
-
* EIP-7702-delegated EOAs are
|
|
12079
|
-
* (`0xef0100<delegate>`) but the underlying secp256k1 key still
|
|
12080
|
-
* `ecrecover`-verifiable signatures, so
|
|
12179
|
+
* EIP-7702-delegated EOAs are treated as EOAs: they expose non-empty
|
|
12180
|
+
* bytecode (`0xef0100<delegate>`) but the underlying secp256k1 key still
|
|
12181
|
+
* produces `ecrecover`-verifiable signatures, so the cheaper EOA path
|
|
12182
|
+
* stays correct for them.
|
|
12081
12183
|
*
|
|
12082
|
-
*
|
|
12083
|
-
*
|
|
12084
|
-
*
|
|
12085
|
-
*
|
|
12086
|
-
* docs for the exact API.
|
|
12087
|
-
*
|
|
12088
|
-
* If bytecode cannot be read (RPC failure, etc.) the pre-check is
|
|
12089
|
-
* skipped and downstream signing surfaces its own error — a warning is
|
|
12090
|
-
* logged so the skip is diagnosable.
|
|
12184
|
+
* If bytecode cannot be read (RPC failure, etc.) the address is reported
|
|
12185
|
+
* as an EOA and a warning is logged so the fallback is diagnosable. A
|
|
12186
|
+
* genuine contract signer misreported this way is rejected by Gateway with
|
|
12187
|
+
* an invalid-signature error rather than silently mis-attested.
|
|
12091
12188
|
*
|
|
12092
12189
|
* @param adapter - Anything exposing {@link EvmAdapterLike.readBytecode}.
|
|
12093
|
-
* @param address - Signer address to
|
|
12190
|
+
* @param address - Signer address to classify.
|
|
12094
12191
|
* @param chain - EVM chain where the signer lives.
|
|
12095
|
-
* @
|
|
12192
|
+
* @returns `true` when the signer is a contract account and the transfer
|
|
12193
|
+
* request must set `contractSigner: true`; `false` otherwise.
|
|
12096
12194
|
*
|
|
12097
12195
|
* @example
|
|
12098
12196
|
* ```typescript
|
|
12099
|
-
* import {
|
|
12197
|
+
* import { isContractSigner } from '@core/adapter-evm'
|
|
12100
12198
|
* import { Ethereum } from '@core/chains'
|
|
12101
12199
|
*
|
|
12102
|
-
* await
|
|
12200
|
+
* const useErc1271 = await isContractSigner(adapter, '0xabc...', Ethereum)
|
|
12103
12201
|
* ```
|
|
12104
|
-
*/ async function
|
|
12202
|
+
*/ async function isContractSigner(adapter, address, chain) {
|
|
12105
12203
|
let code;
|
|
12106
12204
|
try {
|
|
12107
12205
|
code = await adapter.readBytecode(address, chain);
|
|
12108
12206
|
} catch (err) {
|
|
12109
|
-
console.warn(`[gateway]
|
|
12110
|
-
return;
|
|
12207
|
+
console.warn(`[gateway] isContractSigner defaulting to EOA (readBytecode failed ` + `for ${address} on ${chain.name}): ` + (err instanceof Error ? err.message : String(err)));
|
|
12208
|
+
return false;
|
|
12111
12209
|
}
|
|
12112
12210
|
if (code === undefined || code === '0x' || code.toLowerCase().startsWith(EIP_7702_DELEGATION_PREFIX)) {
|
|
12113
|
-
return;
|
|
12211
|
+
return false;
|
|
12114
12212
|
}
|
|
12115
|
-
|
|
12116
|
-
...InputError.UNSUPPORTED_ACTION,
|
|
12117
|
-
recoverability: 'FATAL',
|
|
12118
|
-
message: `Gateway burn-intent signing requires an EOA signer (Gateway ` + `verifies signatures with ecrecover and does not support ERC-1271). ` + `The signer ${address} on ${chain.name} has on-chain bytecode, ` + `indicating it is a smart-contract account (SCA). Register an EOA ` + `delegate against the SCA, then submit the spend with the delegate ` + `EOA as the signer and the SCA as the source account. See DEVX-2774.`,
|
|
12119
|
-
cause: {
|
|
12120
|
-
trace: {
|
|
12121
|
-
operation: 'signEvmIntentGroup.assertSignerIsEoa',
|
|
12122
|
-
address,
|
|
12123
|
-
chain: chain.name,
|
|
12124
|
-
bytecodeBytes: (code.length - 2) / 2,
|
|
12125
|
-
bytecodePrefix: code.slice(0, 12)
|
|
12126
|
-
}
|
|
12127
|
-
}
|
|
12128
|
-
});
|
|
12213
|
+
return true;
|
|
12129
12214
|
}
|
|
12130
12215
|
|
|
12131
12216
|
/**
|
|
@@ -12152,78 +12237,177 @@ function evmSigningData(burnIntent) {
|
|
|
12152
12237
|
return typeof value === 'object' && value !== null && 'readBytecode' in value && typeof value.readBytecode === 'function';
|
|
12153
12238
|
}
|
|
12154
12239
|
|
|
12240
|
+
function resolveIntentChain(group, intent) {
|
|
12241
|
+
const sourceDomain = intent.spec.sourceDomain;
|
|
12242
|
+
const chain = group.chainsByDomain.get(sourceDomain);
|
|
12243
|
+
if (chain !== undefined) return chain;
|
|
12244
|
+
throw createValidationFailedError$1('intent.spec.sourceDomain', sourceDomain, `No source chain found for Gateway domain ${String(sourceDomain)}`);
|
|
12245
|
+
}
|
|
12246
|
+
function normalizeSignatureResult(result) {
|
|
12247
|
+
if (typeof result === 'string') {
|
|
12248
|
+
return {
|
|
12249
|
+
signature: result,
|
|
12250
|
+
contractSigner: false
|
|
12251
|
+
};
|
|
12252
|
+
}
|
|
12253
|
+
if (typeof result === 'object' && result !== null && 'signature' in result && typeof result.signature === 'string') {
|
|
12254
|
+
return {
|
|
12255
|
+
signature: result.signature,
|
|
12256
|
+
contractSigner: 'contractSigner' in result && result.contractSigner === true
|
|
12257
|
+
};
|
|
12258
|
+
}
|
|
12259
|
+
throw createValidationFailedError$1('signature', result, 'must be a signature string or an object containing a signature string');
|
|
12260
|
+
}
|
|
12261
|
+
function validateGroupIntents(intents) {
|
|
12262
|
+
evmSigningData(intents);
|
|
12263
|
+
}
|
|
12264
|
+
function collectChainsByDomain(group) {
|
|
12265
|
+
const chainsByDomain = new Map();
|
|
12266
|
+
for (const intent of group.intents){
|
|
12267
|
+
chainsByDomain.set(intent.spec.sourceDomain, resolveIntentChain(group, intent));
|
|
12268
|
+
}
|
|
12269
|
+
return chainsByDomain;
|
|
12270
|
+
}
|
|
12271
|
+
async function classifySignerTypes(group, chainsByDomain) {
|
|
12272
|
+
const { adapter, address } = group;
|
|
12273
|
+
// Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
|
|
12274
|
+
// each consumer package bundles its own copy of the base class and the
|
|
12275
|
+
// `instanceof` identity check fails across package boundaries.
|
|
12276
|
+
// Empty strings are rejected to avoid calling eth_getCode('') on the RPC.
|
|
12277
|
+
const hasResolvedSigner = typeof address === 'string' && address.length > 0;
|
|
12278
|
+
const signerTypes = await Promise.all([
|
|
12279
|
+
...chainsByDomain
|
|
12280
|
+
].map(async ([sourceDomain, sourceChain])=>{
|
|
12281
|
+
const contractSigner = hasResolvedSigner && sourceChain.type === 'evm' && isEvmAdapterLike(adapter) ? await isContractSigner(adapter, address, sourceChain) : false;
|
|
12282
|
+
return [
|
|
12283
|
+
sourceDomain,
|
|
12284
|
+
contractSigner
|
|
12285
|
+
];
|
|
12286
|
+
}));
|
|
12287
|
+
return new Map(signerTypes);
|
|
12288
|
+
}
|
|
12289
|
+
function createSigningUnits(group, signerTypeByDomain) {
|
|
12290
|
+
const contractUnitsByDomain = new Map();
|
|
12291
|
+
let eoaUnit;
|
|
12292
|
+
for (const [index, intent] of group.intents.entries()){
|
|
12293
|
+
const sourceDomain = intent.spec.sourceDomain;
|
|
12294
|
+
const contractSigner = signerTypeByDomain.get(sourceDomain) ?? false;
|
|
12295
|
+
if (contractSigner) {
|
|
12296
|
+
const existingUnit = contractUnitsByDomain.get(sourceDomain);
|
|
12297
|
+
if (existingUnit === undefined) {
|
|
12298
|
+
contractUnitsByDomain.set(sourceDomain, {
|
|
12299
|
+
intents: [
|
|
12300
|
+
intent
|
|
12301
|
+
],
|
|
12302
|
+
chain: resolveIntentChain(group, intent),
|
|
12303
|
+
contractSigner: true,
|
|
12304
|
+
firstIntentIndex: index
|
|
12305
|
+
});
|
|
12306
|
+
} else {
|
|
12307
|
+
existingUnit.intents.push(intent);
|
|
12308
|
+
}
|
|
12309
|
+
} else {
|
|
12310
|
+
eoaUnit ??= {
|
|
12311
|
+
intents: [],
|
|
12312
|
+
chain: resolveIntentChain(group, intent),
|
|
12313
|
+
contractSigner: false,
|
|
12314
|
+
firstIntentIndex: index
|
|
12315
|
+
};
|
|
12316
|
+
eoaUnit.intents.push(intent);
|
|
12317
|
+
}
|
|
12318
|
+
}
|
|
12319
|
+
const signingUnits = [
|
|
12320
|
+
...contractUnitsByDomain.values()
|
|
12321
|
+
];
|
|
12322
|
+
if (eoaUnit !== undefined) signingUnits.push(eoaUnit);
|
|
12323
|
+
signingUnits.sort((a, b)=>a.firstIntentIndex - b.firstIntentIndex);
|
|
12324
|
+
return signingUnits;
|
|
12325
|
+
}
|
|
12326
|
+
async function signUnit(group, unit) {
|
|
12327
|
+
const { adapter, address } = group;
|
|
12328
|
+
const firstIntent = unit.intents[0];
|
|
12329
|
+
const typedData = unit.intents.length === 1 && firstIntent !== undefined ? evmSigningData(firstIntent) : evmSigningData(unit.intents);
|
|
12330
|
+
const operationContext = address === undefined ? {
|
|
12331
|
+
chain: unit.chain
|
|
12332
|
+
} : {
|
|
12333
|
+
chain: unit.chain,
|
|
12334
|
+
address
|
|
12335
|
+
};
|
|
12336
|
+
const signRequest = await adapter.prepareAction('gateway.v1.signBurnIntents', {
|
|
12337
|
+
typedData,
|
|
12338
|
+
chain: unit.chain
|
|
12339
|
+
}, operationContext);
|
|
12340
|
+
const result = normalizeSignatureResult(await signRequest.execute());
|
|
12341
|
+
return {
|
|
12342
|
+
intents: unit.intents,
|
|
12343
|
+
signature: result.signature,
|
|
12344
|
+
contractSigner: result.contractSigner || unit.contractSigner
|
|
12345
|
+
};
|
|
12346
|
+
}
|
|
12347
|
+
async function signUnits(group, signingUnits) {
|
|
12348
|
+
const signedSets = [];
|
|
12349
|
+
// Keep wallet prompts deterministic. Multiple adapter groups can still sign
|
|
12350
|
+
// in parallel, but one signer is asked for its chain-bound signatures in
|
|
12351
|
+
// source-intent order.
|
|
12352
|
+
for (const unit of signingUnits){
|
|
12353
|
+
signedSets.push(await signUnit(group, unit));
|
|
12354
|
+
}
|
|
12355
|
+
return signedSets;
|
|
12356
|
+
}
|
|
12155
12357
|
/**
|
|
12156
|
-
* Sign an EVM adapter group
|
|
12157
|
-
* EIP-712 ECDSA signature.
|
|
12358
|
+
* Sign an EVM adapter group.
|
|
12158
12359
|
*
|
|
12159
|
-
*
|
|
12160
|
-
*
|
|
12360
|
+
* EOA intents remain batched into one EIP-712 `BurnIntentSet`. ERC-1271
|
|
12361
|
+
* intents are grouped and signed per source chain because smart accounts
|
|
12362
|
+
* commonly include `chainId` in their replay-safe signature hash.
|
|
12363
|
+
* All returned entries can still be submitted together in one atomic Gateway
|
|
12364
|
+
* transfer request.
|
|
12161
12365
|
*
|
|
12162
|
-
* Before signing,
|
|
12163
|
-
*
|
|
12164
|
-
*
|
|
12165
|
-
*
|
|
12166
|
-
*
|
|
12366
|
+
* Before signing, classifies the signer as an EOA or a contract account.
|
|
12367
|
+
* Gateway validates EOA signatures with `ecrecover` and contract-account
|
|
12368
|
+
* signatures with ERC-1271, but it does not infer which one applies — the
|
|
12369
|
+
* transfer request has to declare it. The returned `contractSigner` flag
|
|
12370
|
+
* carries that decision through to `buildTransferRequestBody`.
|
|
12167
12371
|
*
|
|
12168
12372
|
* @param group - The adapter group containing the adapter, chain, and
|
|
12169
12373
|
* burn intents to sign.
|
|
12170
|
-
* @returns
|
|
12374
|
+
* @returns Signed entries with their intents, signatures, and Gateway signer
|
|
12375
|
+
* validation mode.
|
|
12376
|
+
* @throws KitError when an intent has no source-chain mapping or a signing
|
|
12377
|
+
* action returns an invalid signature shape.
|
|
12171
12378
|
*
|
|
12172
12379
|
* @example
|
|
12173
12380
|
* ```typescript
|
|
12174
12381
|
* import { signEvmIntentGroup } from '@core/adapter-evm'
|
|
12175
12382
|
*
|
|
12176
|
-
* const
|
|
12383
|
+
* const signedSets = await signEvmIntentGroup({
|
|
12177
12384
|
* adapter: evmAdapter,
|
|
12178
12385
|
* chain: ethereumChain,
|
|
12179
12386
|
* intents: [burnIntent1, burnIntent2],
|
|
12387
|
+
* chainsByDomain: new Map([
|
|
12388
|
+
* [0, ethereumChain],
|
|
12389
|
+
* [6, baseChain],
|
|
12390
|
+
* ]),
|
|
12180
12391
|
* address: '0x...',
|
|
12181
12392
|
* })
|
|
12182
|
-
* console.log(
|
|
12393
|
+
* console.log(signedSets)
|
|
12183
12394
|
* ```
|
|
12184
12395
|
*/ async function signEvmIntentGroup(group) {
|
|
12185
|
-
|
|
12186
|
-
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
// Gateway verifies burn-intent signatures with plain ecrecover. An SCA
|
|
12193
|
-
// signer silently produces a signature over a wrapped hash that Gateway
|
|
12194
|
-
// cannot verify, and Circle Wallets' KMS rejects the typed data up front
|
|
12195
|
-
// with an opaque `<nil>/<nil>` error. Short-circuit with a clear message
|
|
12196
|
-
// when we can detect bytecode at the signer address. See DEVX-2774.
|
|
12197
|
-
//
|
|
12198
|
-
// Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
|
|
12199
|
-
// each consumer package bundles its own copy of the base class and the
|
|
12200
|
-
// `instanceof` identity check fails across package boundaries.
|
|
12201
|
-
//
|
|
12202
|
-
// Empty string is defended against because assertSignerIsEoa would
|
|
12203
|
-
// otherwise call eth_getCode('') on the RPC.
|
|
12204
|
-
const hasResolvedSigner = typeof address === 'string' && address.length > 0;
|
|
12205
|
-
if (hasResolvedSigner && chain.type === 'evm' && isEvmAdapterLike(adapter)) {
|
|
12206
|
-
await assertSignerIsEoa(adapter, address, chain);
|
|
12207
|
-
}
|
|
12208
|
-
const firstIntent = groupIntents[0];
|
|
12209
|
-
const typedData = groupIntents.length === 1 && firstIntent ? evmSigningData(firstIntent) : evmSigningData(groupIntents);
|
|
12210
|
-
const signRequest = await adapter.prepareAction('gateway.v1.signBurnIntents', {
|
|
12211
|
-
typedData,
|
|
12212
|
-
chain
|
|
12213
|
-
}, operationContext);
|
|
12214
|
-
const sig = await signRequest.execute();
|
|
12215
|
-
return {
|
|
12216
|
-
intents: groupIntents,
|
|
12217
|
-
signature: sig
|
|
12218
|
-
};
|
|
12396
|
+
// Validate the collection before doing bytecode reads or asking a wallet
|
|
12397
|
+
// to sign. evmSigningData owns the canonical BurnIntent validation.
|
|
12398
|
+
validateGroupIntents(group.intents);
|
|
12399
|
+
const chainsByDomain = collectChainsByDomain(group);
|
|
12400
|
+
const signerTypeByDomain = await classifySignerTypes(group, chainsByDomain);
|
|
12401
|
+
const signingUnits = createSigningUnits(group, signerTypeByDomain);
|
|
12402
|
+
return await signUnits(group, signingUnits);
|
|
12219
12403
|
}
|
|
12220
12404
|
|
|
12221
12405
|
/**
|
|
12222
12406
|
* Add an EVM intent into the batched EVM group map.
|
|
12223
12407
|
*
|
|
12224
12408
|
* On EVM, all intents for the same adapter are batched into a single
|
|
12225
|
-
* group
|
|
12226
|
-
*
|
|
12409
|
+
* group. The signing step uses `chainsByDomain` to preserve EOA batching
|
|
12410
|
+
* while signing ERC-1271 intents separately on their source chains.
|
|
12227
12411
|
*
|
|
12228
12412
|
* @param intent - The burn intent to group.
|
|
12229
12413
|
* @param alloc - The allocation that resolved to this intent.
|
|
@@ -12240,6 +12424,7 @@ function evmSigningData(burnIntent) {
|
|
|
12240
12424
|
const existing = evmGroups.get(alloc.adapter);
|
|
12241
12425
|
if (existing) {
|
|
12242
12426
|
existing.intents.push(intent);
|
|
12427
|
+
existing.chainsByDomain.set(alloc.chain.gateway.domain, alloc.chain);
|
|
12243
12428
|
} else {
|
|
12244
12429
|
evmGroups.set(alloc.adapter, {
|
|
12245
12430
|
adapter: alloc.adapter,
|
|
@@ -12247,6 +12432,12 @@ function evmSigningData(burnIntent) {
|
|
|
12247
12432
|
intents: [
|
|
12248
12433
|
intent
|
|
12249
12434
|
],
|
|
12435
|
+
chainsByDomain: new Map([
|
|
12436
|
+
[
|
|
12437
|
+
alloc.chain.gateway.domain,
|
|
12438
|
+
alloc.chain
|
|
12439
|
+
]
|
|
12440
|
+
]),
|
|
12250
12441
|
address: alloc.sourceSigner
|
|
12251
12442
|
});
|
|
12252
12443
|
}
|
|
@@ -13772,7 +13963,8 @@ function throwNetworkMismatch(expected, actual) {
|
|
|
13772
13963
|
};
|
|
13773
13964
|
}
|
|
13774
13965
|
/**
|
|
13775
|
-
* Group intents
|
|
13966
|
+
* Group intents for signing (Solana one-per-intent, EVM batched by adapter
|
|
13967
|
+
* with every source chain retained by Gateway domain).
|
|
13776
13968
|
*
|
|
13777
13969
|
* @param intents - Burn intents from estimate response.
|
|
13778
13970
|
* @param allocations - Normalized allocations used to map domain → adapter/chain.
|
|
@@ -16665,22 +16857,32 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
16665
16857
|
*
|
|
16666
16858
|
* Single-intent sets become one burnIntent + signature; multi-intent sets become burnIntentSet + signature.
|
|
16667
16859
|
*
|
|
16860
|
+
* Sets flagged `contractSigner` carry `contractSigner: true`, which tells
|
|
16861
|
+
* Gateway to validate the signature with ERC-1271 (an offchain
|
|
16862
|
+
* `isValidSignature` simulation) instead of `ecrecover`. The flag is
|
|
16863
|
+
* omitted for EOA signers so their payloads stay byte-identical.
|
|
16864
|
+
*
|
|
16668
16865
|
* @param signedSets - Signed intent sets (intents + signature per signer).
|
|
16669
16866
|
* @returns Array of transfer payloads for POST /v1/transfer.
|
|
16670
16867
|
*/ function buildTransferRequestBody(signedSets) {
|
|
16671
16868
|
return signedSets.map((set)=>{
|
|
16672
16869
|
const firstIntent = set.intents[0];
|
|
16870
|
+
const contractSigner = set.contractSigner === true ? {
|
|
16871
|
+
contractSigner: true
|
|
16872
|
+
} : {};
|
|
16673
16873
|
if (set.intents.length === 1 && firstIntent) {
|
|
16674
16874
|
return {
|
|
16675
16875
|
burnIntent: serializeBurnIntent(firstIntent),
|
|
16676
|
-
signature: set.signature
|
|
16876
|
+
signature: set.signature,
|
|
16877
|
+
...contractSigner
|
|
16677
16878
|
};
|
|
16678
16879
|
}
|
|
16679
16880
|
return {
|
|
16680
16881
|
burnIntentSet: {
|
|
16681
16882
|
intents: set.intents.map(serializeBurnIntent)
|
|
16682
16883
|
},
|
|
16683
|
-
signature: set.signature
|
|
16884
|
+
signature: set.signature,
|
|
16885
|
+
...contractSigner
|
|
16684
16886
|
};
|
|
16685
16887
|
});
|
|
16686
16888
|
}
|
|
@@ -17043,11 +17245,16 @@ const BPS_DIVISOR = 100_000n;
|
|
|
17043
17245
|
return required;
|
|
17044
17246
|
}
|
|
17045
17247
|
|
|
17248
|
+
function requireEvmChainsByDomain(group) {
|
|
17249
|
+
if (group.chainsByDomain !== undefined) return group.chainsByDomain;
|
|
17250
|
+
throw createValidationFailedError$1('adapterGroup.chainsByDomain', group.chainsByDomain, 'must be provided for an EVM adapter group');
|
|
17251
|
+
}
|
|
17046
17252
|
/**
|
|
17047
|
-
* Sign each adapter group: Solana one intent per signature, EVM
|
|
17253
|
+
* Sign each adapter group: Solana one intent per signature, and EVM either
|
|
17254
|
+
* batched for EOAs or split by source chain for ERC-1271 signers.
|
|
17048
17255
|
*
|
|
17049
17256
|
* @param adapterGroups - Groups from groupIntentsByAdapter.
|
|
17050
|
-
* @returns Promise of signed sets
|
|
17257
|
+
* @returns Promise of signed sets for buildTransferRequestBody.
|
|
17051
17258
|
*
|
|
17052
17259
|
* @example
|
|
17053
17260
|
* ```typescript
|
|
@@ -17060,9 +17267,10 @@ const BPS_DIVISOR = 100_000n;
|
|
|
17060
17267
|
if (group.chain.type === 'solana') {
|
|
17061
17268
|
return signSolanaIntentGroup(group);
|
|
17062
17269
|
}
|
|
17063
|
-
return
|
|
17064
|
-
|
|
17065
|
-
|
|
17270
|
+
return await signEvmIntentGroup({
|
|
17271
|
+
...group,
|
|
17272
|
+
chainsByDomain: requireEvmChainsByDomain(group)
|
|
17273
|
+
});
|
|
17066
17274
|
}));
|
|
17067
17275
|
return nested.flat();
|
|
17068
17276
|
}
|
|
@@ -17712,7 +17920,8 @@ async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, s
|
|
|
17712
17920
|
signedSetCount: signedSets.length,
|
|
17713
17921
|
signatures: signedSets.map((s)=>({
|
|
17714
17922
|
intentCount: s.intents.length,
|
|
17715
|
-
signature: s.signature
|
|
17923
|
+
signature: s.signature,
|
|
17924
|
+
contractSigner: s.contractSigner === true
|
|
17716
17925
|
}))
|
|
17717
17926
|
}
|
|
17718
17927
|
});
|