@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.cjs
CHANGED
|
@@ -664,11 +664,6 @@ class KitError extends Error {
|
|
|
664
664
|
name: 'INPUT_UNSUPPORTED_TOKEN',
|
|
665
665
|
type: 'INPUT'
|
|
666
666
|
},
|
|
667
|
-
/** Action not supported by this adapter / ecosystem */ UNSUPPORTED_ACTION: {
|
|
668
|
-
code: 1008,
|
|
669
|
-
name: 'INPUT_UNSUPPORTED_ACTION',
|
|
670
|
-
type: 'INPUT'
|
|
671
|
-
},
|
|
672
667
|
/** No route satisfies the slippage or minimum-output constraint */ SLIPPAGE_CONSTRAINT_NOT_MET: {
|
|
673
668
|
code: 1009,
|
|
674
669
|
name: 'INPUT_SLIPPAGE_CONSTRAINT_NOT_MET',
|
|
@@ -2325,6 +2320,8 @@ class KitError extends Error {
|
|
|
2325
2320
|
Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
2326
2321
|
Blockchain["XDC"] = "XDC";
|
|
2327
2322
|
Blockchain["XDC_Apothem"] = "XDC_Apothem";
|
|
2323
|
+
Blockchain["X_Layer"] = "X_Layer";
|
|
2324
|
+
Blockchain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
2328
2325
|
Blockchain["ZKSync_Era"] = "ZKSync_Era";
|
|
2329
2326
|
Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
|
|
2330
2327
|
})(Blockchain || (Blockchain = {}));
|
|
@@ -2378,6 +2375,7 @@ var BridgeChain;
|
|
|
2378
2375
|
BridgeChain["Unichain"] = "Unichain";
|
|
2379
2376
|
BridgeChain["World_Chain"] = "World_Chain";
|
|
2380
2377
|
BridgeChain["XDC"] = "XDC";
|
|
2378
|
+
BridgeChain["X_Layer"] = "X_Layer";
|
|
2381
2379
|
// Testnet chains with CCTPv2 support
|
|
2382
2380
|
BridgeChain["Arc_Testnet"] = "Arc_Testnet";
|
|
2383
2381
|
BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
@@ -2403,6 +2401,7 @@ var BridgeChain;
|
|
|
2403
2401
|
BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
2404
2402
|
BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
2405
2403
|
BridgeChain["XDC_Apothem"] = "XDC_Apothem";
|
|
2404
|
+
BridgeChain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
2406
2405
|
})(BridgeChain || (BridgeChain = {}));
|
|
2407
2406
|
var UnifiedBalanceChain;
|
|
2408
2407
|
(function(UnifiedBalanceChain) {
|
|
@@ -4950,7 +4949,8 @@ var EarnChain;
|
|
|
4950
4949
|
isTestnet: true,
|
|
4951
4950
|
explorerUrl: 'https://amoy.polygonscan.com/tx/{hash}',
|
|
4952
4951
|
rpcEndpoints: [
|
|
4953
|
-
'https://
|
|
4952
|
+
'https://polygon-amoy-bor-rpc.publicnode.com',
|
|
4953
|
+
'https://polygon-amoy.drpc.org'
|
|
4954
4954
|
],
|
|
4955
4955
|
eurcAddress: null,
|
|
4956
4956
|
usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -5815,6 +5815,104 @@ var EarnChain;
|
|
|
5815
5815
|
}
|
|
5816
5816
|
});
|
|
5817
5817
|
|
|
5818
|
+
/**
|
|
5819
|
+
* X Layer Mainnet chain definition
|
|
5820
|
+
* @remarks
|
|
5821
|
+
* This represents the official production network for the X Layer blockchain.
|
|
5822
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
5823
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
5824
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
5825
|
+
*/ const XLayer = defineChain({
|
|
5826
|
+
type: 'evm',
|
|
5827
|
+
chain: Blockchain.X_Layer,
|
|
5828
|
+
name: 'X Layer',
|
|
5829
|
+
title: 'X Layer Mainnet',
|
|
5830
|
+
nativeCurrency: {
|
|
5831
|
+
name: 'OKB',
|
|
5832
|
+
symbol: 'OKB',
|
|
5833
|
+
decimals: 18
|
|
5834
|
+
},
|
|
5835
|
+
chainId: 196,
|
|
5836
|
+
isTestnet: false,
|
|
5837
|
+
explorerUrl: 'https://www.oklink.com/xlayer/tx/{hash}',
|
|
5838
|
+
rpcEndpoints: [
|
|
5839
|
+
'https://xlayerrpc.okx.com'
|
|
5840
|
+
],
|
|
5841
|
+
eurcAddress: null,
|
|
5842
|
+
usdcAddress: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
5843
|
+
usdtAddress: null,
|
|
5844
|
+
cctp: {
|
|
5845
|
+
domain: 37,
|
|
5846
|
+
contracts: {
|
|
5847
|
+
v2: {
|
|
5848
|
+
type: 'split',
|
|
5849
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5850
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5851
|
+
confirmations: 65,
|
|
5852
|
+
fastConfirmations: 1
|
|
5853
|
+
}
|
|
5854
|
+
},
|
|
5855
|
+
forwarderSupported: {
|
|
5856
|
+
source: false,
|
|
5857
|
+
destination: false
|
|
5858
|
+
}
|
|
5859
|
+
},
|
|
5860
|
+
kitContracts: {
|
|
5861
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
5862
|
+
}
|
|
5863
|
+
});
|
|
5864
|
+
|
|
5865
|
+
/**
|
|
5866
|
+
* X Layer Testnet chain definition
|
|
5867
|
+
* @remarks
|
|
5868
|
+
* This represents the official test network for the X Layer blockchain.
|
|
5869
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
5870
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
5871
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
5872
|
+
*/ const XLayerTestnet = defineChain({
|
|
5873
|
+
type: 'evm',
|
|
5874
|
+
chain: Blockchain.X_Layer_Testnet,
|
|
5875
|
+
name: 'X Layer Testnet',
|
|
5876
|
+
title: 'X Layer Testnet',
|
|
5877
|
+
nativeCurrency: {
|
|
5878
|
+
name: 'OKB',
|
|
5879
|
+
symbol: 'OKB',
|
|
5880
|
+
decimals: 18
|
|
5881
|
+
},
|
|
5882
|
+
chainId: 1952,
|
|
5883
|
+
isTestnet: true,
|
|
5884
|
+
// Deliberately not oklink.com (used for mainnet): viem's bundled OKLink
|
|
5885
|
+
// testnet URL targets the deprecated pre-rebrand chain ID 195, not this
|
|
5886
|
+
// chain's ID (1952). Verified against the internal chain-expansion-scripts
|
|
5887
|
+
// config (`v2config.sandbox.yml`) — do not "normalize" this to match mainnet.
|
|
5888
|
+
explorerUrl: 'https://web3.okx.com/explorer/x-layer-testnet/tx/{hash}',
|
|
5889
|
+
rpcEndpoints: [
|
|
5890
|
+
'https://testrpc.xlayer.tech'
|
|
5891
|
+
],
|
|
5892
|
+
eurcAddress: null,
|
|
5893
|
+
usdcAddress: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
5894
|
+
usdtAddress: null,
|
|
5895
|
+
cctp: {
|
|
5896
|
+
domain: 37,
|
|
5897
|
+
contracts: {
|
|
5898
|
+
v2: {
|
|
5899
|
+
type: 'split',
|
|
5900
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5901
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5902
|
+
confirmations: 65,
|
|
5903
|
+
fastConfirmations: 1
|
|
5904
|
+
}
|
|
5905
|
+
},
|
|
5906
|
+
forwarderSupported: {
|
|
5907
|
+
source: false,
|
|
5908
|
+
destination: false
|
|
5909
|
+
}
|
|
5910
|
+
},
|
|
5911
|
+
kitContracts: {
|
|
5912
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET
|
|
5913
|
+
}
|
|
5914
|
+
});
|
|
5915
|
+
|
|
5818
5916
|
/**
|
|
5819
5917
|
* ZKSync Era Mainnet chain definition
|
|
5820
5918
|
* @remarks
|
|
@@ -5934,6 +6032,8 @@ var Chains = {
|
|
|
5934
6032
|
WorldChainSepolia: WorldChainSepolia,
|
|
5935
6033
|
XDC: XDC,
|
|
5936
6034
|
XDCApothem: XDCApothem,
|
|
6035
|
+
XLayer: XLayer,
|
|
6036
|
+
XLayerTestnet: XLayerTestnet,
|
|
5937
6037
|
ZKSyncEra: ZKSyncEra,
|
|
5938
6038
|
ZKSyncEraSepolia: ZKSyncEraSepolia
|
|
5939
6039
|
};
|
|
@@ -7886,6 +7986,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
7886
7986
|
[Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
7887
7987
|
[Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
|
|
7888
7988
|
[Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
7989
|
+
[Blockchain.X_Layer]: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
7889
7990
|
[Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
|
|
7890
7991
|
// =========================================================================
|
|
7891
7992
|
// Testnets (alphabetically sorted)
|
|
@@ -7920,6 +8021,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
7920
8021
|
[Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
|
|
7921
8022
|
[Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
|
|
7922
8023
|
[Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
|
|
8024
|
+
[Blockchain.X_Layer_Testnet]: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
7923
8025
|
[Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853'
|
|
7924
8026
|
}
|
|
7925
8027
|
};
|
|
@@ -8785,7 +8887,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
8785
8887
|
}
|
|
8786
8888
|
|
|
8787
8889
|
var name = "@circle-fin/unified-balance-kit";
|
|
8788
|
-
var version = "1.
|
|
8890
|
+
var version = "1.4.0";
|
|
8789
8891
|
var pkg = {
|
|
8790
8892
|
name: name,
|
|
8791
8893
|
version: version};
|
|
@@ -12066,72 +12168,55 @@ function evmSigningData(burnIntent) {
|
|
|
12066
12168
|
* `0xef0100` followed by the 20-byte delegate address (23 bytes total).
|
|
12067
12169
|
* The underlying secp256k1 key still produces `ecrecover`-verifiable
|
|
12068
12170
|
* signatures, so for Gateway's purposes a 7702-delegated address is
|
|
12069
|
-
* an EOA, not
|
|
12171
|
+
* an EOA, not a contract signer.
|
|
12070
12172
|
*
|
|
12071
12173
|
* Spec: https://eips.ethereum.org/EIPS/eip-7702
|
|
12072
12174
|
*/ const EIP_7702_DELEGATION_PREFIX = '0xef0100';
|
|
12073
12175
|
/**
|
|
12074
|
-
*
|
|
12176
|
+
* Determine whether `address` on `chain` signs as a contract (ERC-1271)
|
|
12177
|
+
* rather than as an EOA.
|
|
12075
12178
|
*
|
|
12076
|
-
* Gateway
|
|
12077
|
-
* `
|
|
12078
|
-
*
|
|
12079
|
-
* ERC-
|
|
12080
|
-
*
|
|
12081
|
-
* against Gateway's chainId-less domain with an opaque
|
|
12082
|
-
* `invalid integer value <nil>/<nil> for type uint256` error.
|
|
12179
|
+
* Gateway validates burn-intent signatures two ways: a static `ecrecover`
|
|
12180
|
+
* check for EOAs, and — for requests that carry `contractSigner: true` —
|
|
12181
|
+
* an offchain `isValidSignature` simulation against the signing contract
|
|
12182
|
+
* (ERC-1271). Gateway does not infer which one to use, so the caller must
|
|
12183
|
+
* declare it. This detects the contract case from on-chain bytecode.
|
|
12083
12184
|
*
|
|
12084
|
-
* EIP-7702-delegated EOAs are
|
|
12085
|
-
* (`0xef0100<delegate>`) but the underlying secp256k1 key still
|
|
12086
|
-
* `ecrecover`-verifiable signatures, so
|
|
12185
|
+
* EIP-7702-delegated EOAs are treated as EOAs: they expose non-empty
|
|
12186
|
+
* bytecode (`0xef0100<delegate>`) but the underlying secp256k1 key still
|
|
12187
|
+
* produces `ecrecover`-verifiable signatures, so the cheaper EOA path
|
|
12188
|
+
* stays correct for them.
|
|
12087
12189
|
*
|
|
12088
|
-
*
|
|
12089
|
-
*
|
|
12090
|
-
*
|
|
12091
|
-
*
|
|
12092
|
-
* docs for the exact API.
|
|
12093
|
-
*
|
|
12094
|
-
* If bytecode cannot be read (RPC failure, etc.) the pre-check is
|
|
12095
|
-
* skipped and downstream signing surfaces its own error — a warning is
|
|
12096
|
-
* logged so the skip is diagnosable.
|
|
12190
|
+
* If bytecode cannot be read (RPC failure, etc.) the address is reported
|
|
12191
|
+
* as an EOA and a warning is logged so the fallback is diagnosable. A
|
|
12192
|
+
* genuine contract signer misreported this way is rejected by Gateway with
|
|
12193
|
+
* an invalid-signature error rather than silently mis-attested.
|
|
12097
12194
|
*
|
|
12098
12195
|
* @param adapter - Anything exposing {@link EvmAdapterLike.readBytecode}.
|
|
12099
|
-
* @param address - Signer address to
|
|
12196
|
+
* @param address - Signer address to classify.
|
|
12100
12197
|
* @param chain - EVM chain where the signer lives.
|
|
12101
|
-
* @
|
|
12198
|
+
* @returns `true` when the signer is a contract account and the transfer
|
|
12199
|
+
* request must set `contractSigner: true`; `false` otherwise.
|
|
12102
12200
|
*
|
|
12103
12201
|
* @example
|
|
12104
12202
|
* ```typescript
|
|
12105
|
-
* import {
|
|
12203
|
+
* import { isContractSigner } from '@core/adapter-evm'
|
|
12106
12204
|
* import { Ethereum } from '@core/chains'
|
|
12107
12205
|
*
|
|
12108
|
-
* await
|
|
12206
|
+
* const useErc1271 = await isContractSigner(adapter, '0xabc...', Ethereum)
|
|
12109
12207
|
* ```
|
|
12110
|
-
*/ async function
|
|
12208
|
+
*/ async function isContractSigner(adapter, address, chain) {
|
|
12111
12209
|
let code;
|
|
12112
12210
|
try {
|
|
12113
12211
|
code = await adapter.readBytecode(address, chain);
|
|
12114
12212
|
} catch (err) {
|
|
12115
|
-
console.warn(`[gateway]
|
|
12116
|
-
return;
|
|
12213
|
+
console.warn(`[gateway] isContractSigner defaulting to EOA (readBytecode failed ` + `for ${address} on ${chain.name}): ` + (err instanceof Error ? err.message : String(err)));
|
|
12214
|
+
return false;
|
|
12117
12215
|
}
|
|
12118
12216
|
if (code === undefined || code === '0x' || code.toLowerCase().startsWith(EIP_7702_DELEGATION_PREFIX)) {
|
|
12119
|
-
return;
|
|
12217
|
+
return false;
|
|
12120
12218
|
}
|
|
12121
|
-
|
|
12122
|
-
...InputError.UNSUPPORTED_ACTION,
|
|
12123
|
-
recoverability: 'FATAL',
|
|
12124
|
-
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.`,
|
|
12125
|
-
cause: {
|
|
12126
|
-
trace: {
|
|
12127
|
-
operation: 'signEvmIntentGroup.assertSignerIsEoa',
|
|
12128
|
-
address,
|
|
12129
|
-
chain: chain.name,
|
|
12130
|
-
bytecodeBytes: (code.length - 2) / 2,
|
|
12131
|
-
bytecodePrefix: code.slice(0, 12)
|
|
12132
|
-
}
|
|
12133
|
-
}
|
|
12134
|
-
});
|
|
12219
|
+
return true;
|
|
12135
12220
|
}
|
|
12136
12221
|
|
|
12137
12222
|
/**
|
|
@@ -12158,78 +12243,177 @@ function evmSigningData(burnIntent) {
|
|
|
12158
12243
|
return typeof value === 'object' && value !== null && 'readBytecode' in value && typeof value.readBytecode === 'function';
|
|
12159
12244
|
}
|
|
12160
12245
|
|
|
12246
|
+
function resolveIntentChain(group, intent) {
|
|
12247
|
+
const sourceDomain = intent.spec.sourceDomain;
|
|
12248
|
+
const chain = group.chainsByDomain.get(sourceDomain);
|
|
12249
|
+
if (chain !== undefined) return chain;
|
|
12250
|
+
throw createValidationFailedError$1('intent.spec.sourceDomain', sourceDomain, `No source chain found for Gateway domain ${String(sourceDomain)}`);
|
|
12251
|
+
}
|
|
12252
|
+
function normalizeSignatureResult(result) {
|
|
12253
|
+
if (typeof result === 'string') {
|
|
12254
|
+
return {
|
|
12255
|
+
signature: result,
|
|
12256
|
+
contractSigner: false
|
|
12257
|
+
};
|
|
12258
|
+
}
|
|
12259
|
+
if (typeof result === 'object' && result !== null && 'signature' in result && typeof result.signature === 'string') {
|
|
12260
|
+
return {
|
|
12261
|
+
signature: result.signature,
|
|
12262
|
+
contractSigner: 'contractSigner' in result && result.contractSigner === true
|
|
12263
|
+
};
|
|
12264
|
+
}
|
|
12265
|
+
throw createValidationFailedError$1('signature', result, 'must be a signature string or an object containing a signature string');
|
|
12266
|
+
}
|
|
12267
|
+
function validateGroupIntents(intents) {
|
|
12268
|
+
evmSigningData(intents);
|
|
12269
|
+
}
|
|
12270
|
+
function collectChainsByDomain(group) {
|
|
12271
|
+
const chainsByDomain = new Map();
|
|
12272
|
+
for (const intent of group.intents){
|
|
12273
|
+
chainsByDomain.set(intent.spec.sourceDomain, resolveIntentChain(group, intent));
|
|
12274
|
+
}
|
|
12275
|
+
return chainsByDomain;
|
|
12276
|
+
}
|
|
12277
|
+
async function classifySignerTypes(group, chainsByDomain) {
|
|
12278
|
+
const { adapter, address } = group;
|
|
12279
|
+
// Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
|
|
12280
|
+
// each consumer package bundles its own copy of the base class and the
|
|
12281
|
+
// `instanceof` identity check fails across package boundaries.
|
|
12282
|
+
// Empty strings are rejected to avoid calling eth_getCode('') on the RPC.
|
|
12283
|
+
const hasResolvedSigner = typeof address === 'string' && address.length > 0;
|
|
12284
|
+
const signerTypes = await Promise.all([
|
|
12285
|
+
...chainsByDomain
|
|
12286
|
+
].map(async ([sourceDomain, sourceChain])=>{
|
|
12287
|
+
const contractSigner = hasResolvedSigner && sourceChain.type === 'evm' && isEvmAdapterLike(adapter) ? await isContractSigner(adapter, address, sourceChain) : false;
|
|
12288
|
+
return [
|
|
12289
|
+
sourceDomain,
|
|
12290
|
+
contractSigner
|
|
12291
|
+
];
|
|
12292
|
+
}));
|
|
12293
|
+
return new Map(signerTypes);
|
|
12294
|
+
}
|
|
12295
|
+
function createSigningUnits(group, signerTypeByDomain) {
|
|
12296
|
+
const contractUnitsByDomain = new Map();
|
|
12297
|
+
let eoaUnit;
|
|
12298
|
+
for (const [index, intent] of group.intents.entries()){
|
|
12299
|
+
const sourceDomain = intent.spec.sourceDomain;
|
|
12300
|
+
const contractSigner = signerTypeByDomain.get(sourceDomain) ?? false;
|
|
12301
|
+
if (contractSigner) {
|
|
12302
|
+
const existingUnit = contractUnitsByDomain.get(sourceDomain);
|
|
12303
|
+
if (existingUnit === undefined) {
|
|
12304
|
+
contractUnitsByDomain.set(sourceDomain, {
|
|
12305
|
+
intents: [
|
|
12306
|
+
intent
|
|
12307
|
+
],
|
|
12308
|
+
chain: resolveIntentChain(group, intent),
|
|
12309
|
+
contractSigner: true,
|
|
12310
|
+
firstIntentIndex: index
|
|
12311
|
+
});
|
|
12312
|
+
} else {
|
|
12313
|
+
existingUnit.intents.push(intent);
|
|
12314
|
+
}
|
|
12315
|
+
} else {
|
|
12316
|
+
eoaUnit ??= {
|
|
12317
|
+
intents: [],
|
|
12318
|
+
chain: resolveIntentChain(group, intent),
|
|
12319
|
+
contractSigner: false,
|
|
12320
|
+
firstIntentIndex: index
|
|
12321
|
+
};
|
|
12322
|
+
eoaUnit.intents.push(intent);
|
|
12323
|
+
}
|
|
12324
|
+
}
|
|
12325
|
+
const signingUnits = [
|
|
12326
|
+
...contractUnitsByDomain.values()
|
|
12327
|
+
];
|
|
12328
|
+
if (eoaUnit !== undefined) signingUnits.push(eoaUnit);
|
|
12329
|
+
signingUnits.sort((a, b)=>a.firstIntentIndex - b.firstIntentIndex);
|
|
12330
|
+
return signingUnits;
|
|
12331
|
+
}
|
|
12332
|
+
async function signUnit(group, unit) {
|
|
12333
|
+
const { adapter, address } = group;
|
|
12334
|
+
const firstIntent = unit.intents[0];
|
|
12335
|
+
const typedData = unit.intents.length === 1 && firstIntent !== undefined ? evmSigningData(firstIntent) : evmSigningData(unit.intents);
|
|
12336
|
+
const operationContext = address === undefined ? {
|
|
12337
|
+
chain: unit.chain
|
|
12338
|
+
} : {
|
|
12339
|
+
chain: unit.chain,
|
|
12340
|
+
address
|
|
12341
|
+
};
|
|
12342
|
+
const signRequest = await adapter.prepareAction('gateway.v1.signBurnIntents', {
|
|
12343
|
+
typedData,
|
|
12344
|
+
chain: unit.chain
|
|
12345
|
+
}, operationContext);
|
|
12346
|
+
const result = normalizeSignatureResult(await signRequest.execute());
|
|
12347
|
+
return {
|
|
12348
|
+
intents: unit.intents,
|
|
12349
|
+
signature: result.signature,
|
|
12350
|
+
contractSigner: result.contractSigner || unit.contractSigner
|
|
12351
|
+
};
|
|
12352
|
+
}
|
|
12353
|
+
async function signUnits(group, signingUnits) {
|
|
12354
|
+
const signedSets = [];
|
|
12355
|
+
// Keep wallet prompts deterministic. Multiple adapter groups can still sign
|
|
12356
|
+
// in parallel, but one signer is asked for its chain-bound signatures in
|
|
12357
|
+
// source-intent order.
|
|
12358
|
+
for (const unit of signingUnits){
|
|
12359
|
+
signedSets.push(await signUnit(group, unit));
|
|
12360
|
+
}
|
|
12361
|
+
return signedSets;
|
|
12362
|
+
}
|
|
12161
12363
|
/**
|
|
12162
|
-
* Sign an EVM adapter group
|
|
12163
|
-
* EIP-712 ECDSA signature.
|
|
12364
|
+
* Sign an EVM adapter group.
|
|
12164
12365
|
*
|
|
12165
|
-
*
|
|
12166
|
-
*
|
|
12366
|
+
* EOA intents remain batched into one EIP-712 `BurnIntentSet`. ERC-1271
|
|
12367
|
+
* intents are grouped and signed per source chain because smart accounts
|
|
12368
|
+
* commonly include `chainId` in their replay-safe signature hash.
|
|
12369
|
+
* All returned entries can still be submitted together in one atomic Gateway
|
|
12370
|
+
* transfer request.
|
|
12167
12371
|
*
|
|
12168
|
-
* Before signing,
|
|
12169
|
-
*
|
|
12170
|
-
*
|
|
12171
|
-
*
|
|
12172
|
-
*
|
|
12372
|
+
* Before signing, classifies the signer as an EOA or a contract account.
|
|
12373
|
+
* Gateway validates EOA signatures with `ecrecover` and contract-account
|
|
12374
|
+
* signatures with ERC-1271, but it does not infer which one applies — the
|
|
12375
|
+
* transfer request has to declare it. The returned `contractSigner` flag
|
|
12376
|
+
* carries that decision through to `buildTransferRequestBody`.
|
|
12173
12377
|
*
|
|
12174
12378
|
* @param group - The adapter group containing the adapter, chain, and
|
|
12175
12379
|
* burn intents to sign.
|
|
12176
|
-
* @returns
|
|
12380
|
+
* @returns Signed entries with their intents, signatures, and Gateway signer
|
|
12381
|
+
* validation mode.
|
|
12382
|
+
* @throws KitError when an intent has no source-chain mapping or a signing
|
|
12383
|
+
* action returns an invalid signature shape.
|
|
12177
12384
|
*
|
|
12178
12385
|
* @example
|
|
12179
12386
|
* ```typescript
|
|
12180
12387
|
* import { signEvmIntentGroup } from '@core/adapter-evm'
|
|
12181
12388
|
*
|
|
12182
|
-
* const
|
|
12389
|
+
* const signedSets = await signEvmIntentGroup({
|
|
12183
12390
|
* adapter: evmAdapter,
|
|
12184
12391
|
* chain: ethereumChain,
|
|
12185
12392
|
* intents: [burnIntent1, burnIntent2],
|
|
12393
|
+
* chainsByDomain: new Map([
|
|
12394
|
+
* [0, ethereumChain],
|
|
12395
|
+
* [6, baseChain],
|
|
12396
|
+
* ]),
|
|
12186
12397
|
* address: '0x...',
|
|
12187
12398
|
* })
|
|
12188
|
-
* console.log(
|
|
12399
|
+
* console.log(signedSets)
|
|
12189
12400
|
* ```
|
|
12190
12401
|
*/ async function signEvmIntentGroup(group) {
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
// Gateway verifies burn-intent signatures with plain ecrecover. An SCA
|
|
12199
|
-
// signer silently produces a signature over a wrapped hash that Gateway
|
|
12200
|
-
// cannot verify, and Circle Wallets' KMS rejects the typed data up front
|
|
12201
|
-
// with an opaque `<nil>/<nil>` error. Short-circuit with a clear message
|
|
12202
|
-
// when we can detect bytecode at the signer address. See DEVX-2774.
|
|
12203
|
-
//
|
|
12204
|
-
// Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
|
|
12205
|
-
// each consumer package bundles its own copy of the base class and the
|
|
12206
|
-
// `instanceof` identity check fails across package boundaries.
|
|
12207
|
-
//
|
|
12208
|
-
// Empty string is defended against because assertSignerIsEoa would
|
|
12209
|
-
// otherwise call eth_getCode('') on the RPC.
|
|
12210
|
-
const hasResolvedSigner = typeof address === 'string' && address.length > 0;
|
|
12211
|
-
if (hasResolvedSigner && chain.type === 'evm' && isEvmAdapterLike(adapter)) {
|
|
12212
|
-
await assertSignerIsEoa(adapter, address, chain);
|
|
12213
|
-
}
|
|
12214
|
-
const firstIntent = groupIntents[0];
|
|
12215
|
-
const typedData = groupIntents.length === 1 && firstIntent ? evmSigningData(firstIntent) : evmSigningData(groupIntents);
|
|
12216
|
-
const signRequest = await adapter.prepareAction('gateway.v1.signBurnIntents', {
|
|
12217
|
-
typedData,
|
|
12218
|
-
chain
|
|
12219
|
-
}, operationContext);
|
|
12220
|
-
const sig = await signRequest.execute();
|
|
12221
|
-
return {
|
|
12222
|
-
intents: groupIntents,
|
|
12223
|
-
signature: sig
|
|
12224
|
-
};
|
|
12402
|
+
// Validate the collection before doing bytecode reads or asking a wallet
|
|
12403
|
+
// to sign. evmSigningData owns the canonical BurnIntent validation.
|
|
12404
|
+
validateGroupIntents(group.intents);
|
|
12405
|
+
const chainsByDomain = collectChainsByDomain(group);
|
|
12406
|
+
const signerTypeByDomain = await classifySignerTypes(group, chainsByDomain);
|
|
12407
|
+
const signingUnits = createSigningUnits(group, signerTypeByDomain);
|
|
12408
|
+
return await signUnits(group, signingUnits);
|
|
12225
12409
|
}
|
|
12226
12410
|
|
|
12227
12411
|
/**
|
|
12228
12412
|
* Add an EVM intent into the batched EVM group map.
|
|
12229
12413
|
*
|
|
12230
12414
|
* On EVM, all intents for the same adapter are batched into a single
|
|
12231
|
-
* group
|
|
12232
|
-
*
|
|
12415
|
+
* group. The signing step uses `chainsByDomain` to preserve EOA batching
|
|
12416
|
+
* while signing ERC-1271 intents separately on their source chains.
|
|
12233
12417
|
*
|
|
12234
12418
|
* @param intent - The burn intent to group.
|
|
12235
12419
|
* @param alloc - The allocation that resolved to this intent.
|
|
@@ -12246,6 +12430,7 @@ function evmSigningData(burnIntent) {
|
|
|
12246
12430
|
const existing = evmGroups.get(alloc.adapter);
|
|
12247
12431
|
if (existing) {
|
|
12248
12432
|
existing.intents.push(intent);
|
|
12433
|
+
existing.chainsByDomain.set(alloc.chain.gateway.domain, alloc.chain);
|
|
12249
12434
|
} else {
|
|
12250
12435
|
evmGroups.set(alloc.adapter, {
|
|
12251
12436
|
adapter: alloc.adapter,
|
|
@@ -12253,6 +12438,12 @@ function evmSigningData(burnIntent) {
|
|
|
12253
12438
|
intents: [
|
|
12254
12439
|
intent
|
|
12255
12440
|
],
|
|
12441
|
+
chainsByDomain: new Map([
|
|
12442
|
+
[
|
|
12443
|
+
alloc.chain.gateway.domain,
|
|
12444
|
+
alloc.chain
|
|
12445
|
+
]
|
|
12446
|
+
]),
|
|
12256
12447
|
address: alloc.sourceSigner
|
|
12257
12448
|
});
|
|
12258
12449
|
}
|
|
@@ -13778,7 +13969,8 @@ function throwNetworkMismatch(expected, actual) {
|
|
|
13778
13969
|
};
|
|
13779
13970
|
}
|
|
13780
13971
|
/**
|
|
13781
|
-
* Group intents
|
|
13972
|
+
* Group intents for signing (Solana one-per-intent, EVM batched by adapter
|
|
13973
|
+
* with every source chain retained by Gateway domain).
|
|
13782
13974
|
*
|
|
13783
13975
|
* @param intents - Burn intents from estimate response.
|
|
13784
13976
|
* @param allocations - Normalized allocations used to map domain → adapter/chain.
|
|
@@ -16671,22 +16863,32 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
16671
16863
|
*
|
|
16672
16864
|
* Single-intent sets become one burnIntent + signature; multi-intent sets become burnIntentSet + signature.
|
|
16673
16865
|
*
|
|
16866
|
+
* Sets flagged `contractSigner` carry `contractSigner: true`, which tells
|
|
16867
|
+
* Gateway to validate the signature with ERC-1271 (an offchain
|
|
16868
|
+
* `isValidSignature` simulation) instead of `ecrecover`. The flag is
|
|
16869
|
+
* omitted for EOA signers so their payloads stay byte-identical.
|
|
16870
|
+
*
|
|
16674
16871
|
* @param signedSets - Signed intent sets (intents + signature per signer).
|
|
16675
16872
|
* @returns Array of transfer payloads for POST /v1/transfer.
|
|
16676
16873
|
*/ function buildTransferRequestBody(signedSets) {
|
|
16677
16874
|
return signedSets.map((set)=>{
|
|
16678
16875
|
const firstIntent = set.intents[0];
|
|
16876
|
+
const contractSigner = set.contractSigner === true ? {
|
|
16877
|
+
contractSigner: true
|
|
16878
|
+
} : {};
|
|
16679
16879
|
if (set.intents.length === 1 && firstIntent) {
|
|
16680
16880
|
return {
|
|
16681
16881
|
burnIntent: serializeBurnIntent(firstIntent),
|
|
16682
|
-
signature: set.signature
|
|
16882
|
+
signature: set.signature,
|
|
16883
|
+
...contractSigner
|
|
16683
16884
|
};
|
|
16684
16885
|
}
|
|
16685
16886
|
return {
|
|
16686
16887
|
burnIntentSet: {
|
|
16687
16888
|
intents: set.intents.map(serializeBurnIntent)
|
|
16688
16889
|
},
|
|
16689
|
-
signature: set.signature
|
|
16890
|
+
signature: set.signature,
|
|
16891
|
+
...contractSigner
|
|
16690
16892
|
};
|
|
16691
16893
|
});
|
|
16692
16894
|
}
|
|
@@ -17049,11 +17251,16 @@ const BPS_DIVISOR = 100_000n;
|
|
|
17049
17251
|
return required;
|
|
17050
17252
|
}
|
|
17051
17253
|
|
|
17254
|
+
function requireEvmChainsByDomain(group) {
|
|
17255
|
+
if (group.chainsByDomain !== undefined) return group.chainsByDomain;
|
|
17256
|
+
throw createValidationFailedError$1('adapterGroup.chainsByDomain', group.chainsByDomain, 'must be provided for an EVM adapter group');
|
|
17257
|
+
}
|
|
17052
17258
|
/**
|
|
17053
|
-
* Sign each adapter group: Solana one intent per signature, EVM
|
|
17259
|
+
* Sign each adapter group: Solana one intent per signature, and EVM either
|
|
17260
|
+
* batched for EOAs or split by source chain for ERC-1271 signers.
|
|
17054
17261
|
*
|
|
17055
17262
|
* @param adapterGroups - Groups from groupIntentsByAdapter.
|
|
17056
|
-
* @returns Promise of signed sets
|
|
17263
|
+
* @returns Promise of signed sets for buildTransferRequestBody.
|
|
17057
17264
|
*
|
|
17058
17265
|
* @example
|
|
17059
17266
|
* ```typescript
|
|
@@ -17066,9 +17273,10 @@ const BPS_DIVISOR = 100_000n;
|
|
|
17066
17273
|
if (group.chain.type === 'solana') {
|
|
17067
17274
|
return signSolanaIntentGroup(group);
|
|
17068
17275
|
}
|
|
17069
|
-
return
|
|
17070
|
-
|
|
17071
|
-
|
|
17276
|
+
return await signEvmIntentGroup({
|
|
17277
|
+
...group,
|
|
17278
|
+
chainsByDomain: requireEvmChainsByDomain(group)
|
|
17279
|
+
});
|
|
17072
17280
|
}));
|
|
17073
17281
|
return nested.flat();
|
|
17074
17282
|
}
|
|
@@ -17718,7 +17926,8 @@ async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, s
|
|
|
17718
17926
|
signedSetCount: signedSets.length,
|
|
17719
17927
|
signatures: signedSets.map((s)=>({
|
|
17720
17928
|
intentCount: s.intents.length,
|
|
17721
|
-
signature: s.signature
|
|
17929
|
+
signature: s.signature,
|
|
17930
|
+
contractSigner: s.contractSigner === true
|
|
17722
17931
|
}))
|
|
17723
17932
|
}
|
|
17724
17933
|
});
|
package/unifiedBalance.d.cts
CHANGED
|
@@ -739,6 +739,8 @@ declare enum Blockchain {
|
|
|
739
739
|
World_Chain_Sepolia = "World_Chain_Sepolia",
|
|
740
740
|
XDC = "XDC",
|
|
741
741
|
XDC_Apothem = "XDC_Apothem",
|
|
742
|
+
X_Layer = "X_Layer",
|
|
743
|
+
X_Layer_Testnet = "X_Layer_Testnet",
|
|
742
744
|
ZKSync_Era = "ZKSync_Era",
|
|
743
745
|
ZKSync_Sepolia = "ZKSync_Sepolia"
|
|
744
746
|
}
|
package/unifiedBalance.d.mts
CHANGED
|
@@ -739,6 +739,8 @@ declare enum Blockchain {
|
|
|
739
739
|
World_Chain_Sepolia = "World_Chain_Sepolia",
|
|
740
740
|
XDC = "XDC",
|
|
741
741
|
XDC_Apothem = "XDC_Apothem",
|
|
742
|
+
X_Layer = "X_Layer",
|
|
743
|
+
X_Layer_Testnet = "X_Layer_Testnet",
|
|
742
744
|
ZKSync_Era = "ZKSync_Era",
|
|
743
745
|
ZKSync_Sepolia = "ZKSync_Sepolia"
|
|
744
746
|
}
|
package/unifiedBalance.d.ts
CHANGED
|
@@ -739,6 +739,8 @@ declare enum Blockchain {
|
|
|
739
739
|
World_Chain_Sepolia = "World_Chain_Sepolia",
|
|
740
740
|
XDC = "XDC",
|
|
741
741
|
XDC_Apothem = "XDC_Apothem",
|
|
742
|
+
X_Layer = "X_Layer",
|
|
743
|
+
X_Layer_Testnet = "X_Layer_Testnet",
|
|
742
744
|
ZKSync_Era = "ZKSync_Era",
|
|
743
745
|
ZKSync_Sepolia = "ZKSync_Sepolia"
|
|
744
746
|
}
|