@0xsquid/react-hooks 8.11.1-beta-danal.2 → 8.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/dist/core/canton/cantonConnector.d.ts +5 -2
- package/dist/core/canton/errors.d.ts +4 -0
- package/dist/core/canton/loopCantonConnector.d.ts +38 -0
- package/dist/core/client/index.d.ts +0 -1
- package/dist/core/constants.d.ts +0 -2
- package/dist/core/types/canton.d.ts +37 -3
- package/dist/core/types/index.d.ts +1 -1
- package/dist/core/types/tokens.d.ts +1 -8
- package/dist/core/types/wallet.d.ts +1 -8
- package/dist/hooks/chains/useSquidChains.d.ts +0 -1
- package/dist/hooks/store/useDepositAddressStore.d.ts +0 -6
- package/dist/hooks/swap/useDepositAddress.d.ts +0 -2
- package/dist/hooks/tokens/useSquidTokens.d.ts +0 -1
- package/dist/hooks/wallet/useMultiChainWallet.d.ts +0 -38
- package/dist/hooks/wallet/useWallet.d.ts +0 -42
- package/dist/{index-ByakOYXP.js → index-C3owE9lb.js} +740 -514
- package/dist/index-C3owE9lb.js.map +1 -0
- package/dist/{index-nTqoaD0B.js → index-CJVX-7hj.js} +739 -517
- package/dist/index-CJVX-7hj.js.map +1 -0
- package/dist/{index.es--AR2n7a0.js → index.es-09IsADsA.js} +2 -2
- package/dist/{index.es--AR2n7a0.js.map → index.es-09IsADsA.js.map} +1 -1
- package/dist/{index.es-DtFDz6qc.js → index.es-CTKuR0LC.js} +2 -2
- package/dist/{index.es-DtFDz6qc.js.map → index.es-CTKuR0LC.js.map} +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/{secretService-cgr3P1zw.js → secretService--tJFCkzT.js} +2 -2
- package/dist/{secretService-cgr3P1zw.js.map → secretService--tJFCkzT.js.map} +1 -1
- package/dist/{secretService-29Ak2qp5.js → secretService-GHdReJBs.js} +2 -2
- package/dist/{secretService-29Ak2qp5.js.map → secretService-GHdReJBs.js.map} +1 -1
- package/dist/server.esm.js +0 -6
- package/dist/server.esm.js.map +1 -1
- package/dist/server.js +0 -6
- package/dist/server.js.map +1 -1
- package/dist/services/external/rpcService.d.ts +2 -4
- package/dist/services/index.d.ts +0 -1
- package/dist/services/internal/assetsService.d.ts +1 -1
- package/dist/services/internal/cantonService.d.ts +2 -0
- package/dist/services/internal/cantonTransferService.d.ts +3 -3
- package/dist/services/internal/estimateService.d.ts +1 -1
- package/dist/services/internal/searchService.d.ts +1 -2
- package/dist/services/internal/transactionService.d.ts +0 -8
- package/dist/{stellarService.client-B__dTUBZ.js → stellarService.client-DAxgO3m3.js} +2 -2
- package/dist/{stellarService.client-B__dTUBZ.js.map → stellarService.client-DAxgO3m3.js.map} +1 -1
- package/dist/{stellarService.client-D-zkMI5s.js → stellarService.client-DvA-2pXq.js} +2 -2
- package/dist/{stellarService.client-D-zkMI5s.js.map → stellarService.client-DvA-2pXq.js.map} +1 -1
- package/dist/tests/loopSdkContract.test.d.ts +1 -0
- package/dist/tests/useAutoConnectCanton.test.d.ts +1 -0
- package/dist/tests/useCantonRestoration.test.d.ts +1 -0
- package/package.json +3 -2
- package/dist/core/types/danal.d.ts +0 -1
- package/dist/index-ByakOYXP.js.map +0 -1
- package/dist/index-nTqoaD0B.js.map +0 -1
- package/dist/services/internal/danalService.d.ts +0 -8
- /package/dist/tests/{danalService.test.d.ts → loopCantonConnector.test.d.ts} +0 -0
|
@@ -59,7 +59,6 @@ const suiZeroAddress = "0x0000000000000000000000000000000000000000";
|
|
|
59
59
|
const xrplZeroAddress = "rrrrrrrrrrrrrrrrrrrrrhoLvTp";
|
|
60
60
|
const stellarZeroAddress = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF";
|
|
61
61
|
const cantonZeroAddress = "none::12200000000000000000000000000000000000000000000000000000000000000000";
|
|
62
|
-
const danalZeroAddress = "PCI00000000000000000000000000000000000000000000000000";
|
|
63
62
|
const chainTypeToZeroAddressMap = {
|
|
64
63
|
[ChainType.EVM]: zeroAddress$1,
|
|
65
64
|
[ChainType.COSMOS]: cosmosZeroAddress,
|
|
@@ -69,7 +68,6 @@ const chainTypeToZeroAddressMap = {
|
|
|
69
68
|
[ChainType.XRPL]: xrplZeroAddress,
|
|
70
69
|
[ChainType.STELLAR]: stellarZeroAddress,
|
|
71
70
|
[ChainType.CANTON]: cantonZeroAddress,
|
|
72
|
-
[ChainType.DANAL]: danalZeroAddress,
|
|
73
71
|
};
|
|
74
72
|
const nativeEvmTokenAddress = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
75
73
|
const nativeCosmosTokenAddress = "uosmo";
|
|
@@ -79,7 +77,6 @@ const nativeSuiTokenAddress = "0x00000000000000000000000000000000000000000000000
|
|
|
79
77
|
const nativeXrplTokenAddress = "xrp";
|
|
80
78
|
const nativeStellarTokenAddress = "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA";
|
|
81
79
|
const nativeCantonTokenAddress = "DSO::1220b1431ef217342db44d516bb9befde802be7d8899637d290895fa58880f19accc";
|
|
82
|
-
const nativeDanalTokenAddress = "pci";
|
|
83
80
|
// by setting slippage to undefined, it's set to "auto"
|
|
84
81
|
const defaultSlippage = undefined;
|
|
85
82
|
const destinationAddressResetValue = "null";
|
|
@@ -145,7 +142,6 @@ const CHAIN_IDS = {
|
|
|
145
142
|
STELLAR: "stellar-mainnet",
|
|
146
143
|
STELLAR_TESTNET: "stellar-testnet",
|
|
147
144
|
CANTON: "canton",
|
|
148
|
-
DANAL_TESTNET: "danal-testnet",
|
|
149
145
|
};
|
|
150
146
|
const chainTypeToDefaultChainIdMap = {
|
|
151
147
|
[ChainType.EVM]: CHAIN_IDS.ETHEREUM,
|
|
@@ -156,7 +152,6 @@ const chainTypeToDefaultChainIdMap = {
|
|
|
156
152
|
[ChainType.XRPL]: CHAIN_IDS.XRPL,
|
|
157
153
|
[ChainType.STELLAR]: CHAIN_IDS.STELLAR,
|
|
158
154
|
[ChainType.CANTON]: CHAIN_IDS.CANTON,
|
|
159
|
-
[ChainType.DANAL]: CHAIN_IDS.DANAL_TESTNET,
|
|
160
155
|
};
|
|
161
156
|
const chainTypeToNativeTokenAddressMap = {
|
|
162
157
|
[ChainType.EVM]: nativeEvmTokenAddress,
|
|
@@ -167,7 +162,6 @@ const chainTypeToNativeTokenAddressMap = {
|
|
|
167
162
|
[ChainType.XRPL]: nativeXrplTokenAddress,
|
|
168
163
|
[ChainType.STELLAR]: nativeStellarTokenAddress,
|
|
169
164
|
[ChainType.CANTON]: nativeCantonTokenAddress,
|
|
170
|
-
[ChainType.DANAL]: nativeDanalTokenAddress,
|
|
171
165
|
};
|
|
172
166
|
const defaultConfigValues = {
|
|
173
167
|
integratorId: INTEGRATOR_ID,
|
|
@@ -20949,6 +20943,9 @@ const isBitcoinAddressValid = (address$1) => {
|
|
|
20949
20943
|
}
|
|
20950
20944
|
};
|
|
20951
20945
|
|
|
20946
|
+
function getCantonCommandId(memo) {
|
|
20947
|
+
return `canton-${memo}`;
|
|
20948
|
+
}
|
|
20952
20949
|
/**
|
|
20953
20950
|
* Format a Canton connector into a Squid {@link CantonWallet}. Shared by the
|
|
20954
20951
|
* announce-discovery and injected-provider paths.
|
|
@@ -21099,51 +21096,16 @@ function cantonInstrumentKey(instrument) {
|
|
|
21099
21096
|
* is already a Decimal, not base units).
|
|
21100
21097
|
*/
|
|
21101
21098
|
function groupCantonHoldingBalances(acsResponse) {
|
|
21099
|
+
return sumCantonHoldingBalances(parseCantonHoldings(acsResponse).filter((holding) => !holding.locked));
|
|
21100
|
+
}
|
|
21101
|
+
function sumCantonHoldingBalances(holdings) {
|
|
21102
21102
|
const totals = {};
|
|
21103
|
-
for (const holding of
|
|
21104
|
-
// Locked holdings are not spendable, skip them
|
|
21105
|
-
if (holding.locked)
|
|
21106
|
-
continue;
|
|
21103
|
+
for (const holding of holdings) {
|
|
21107
21104
|
const key = cantonInstrumentKey(holding.instrument);
|
|
21108
21105
|
const total = totals[key] ?? new BigNumber(0);
|
|
21109
21106
|
totals[key] = total.plus(new BigNumber(holding.amount));
|
|
21110
21107
|
}
|
|
21111
|
-
return Object.fromEntries(Object.entries(totals).map(([key, total]) => [key, total.
|
|
21112
|
-
}
|
|
21113
|
-
|
|
21114
|
-
// Matches the backend validation: "PCI" + 50 uppercase-hex chars, case-sensitive.
|
|
21115
|
-
const DANAL_ADDRESS_REGEX = /^PCI[0-9A-F]{50}$/;
|
|
21116
|
-
function isDanalAddressValid(address) {
|
|
21117
|
-
if (!address)
|
|
21118
|
-
return false;
|
|
21119
|
-
return DANAL_ADDRESS_REGEX.test(address);
|
|
21120
|
-
}
|
|
21121
|
-
// Fabric transaction id: 64 hex chars, no 0x prefix
|
|
21122
|
-
const DANAL_TX_HASH_REGEX = /^[0-9a-fA-F]{64}$/;
|
|
21123
|
-
function isDanalTxHashValid(hash) {
|
|
21124
|
-
if (!hash)
|
|
21125
|
-
return false;
|
|
21126
|
-
return DANAL_TX_HASH_REGEX.test(hash);
|
|
21127
|
-
}
|
|
21128
|
-
function isDanalTransferRequestBody(value) {
|
|
21129
|
-
if (typeof value !== "object" || value === null)
|
|
21130
|
-
return false;
|
|
21131
|
-
const { receiver, amount, memo } = value;
|
|
21132
|
-
return (typeof receiver === "string" &&
|
|
21133
|
-
receiver.length > 0 &&
|
|
21134
|
-
typeof amount === "string" &&
|
|
21135
|
-
amount.length > 0 &&
|
|
21136
|
-
typeof memo === "string" &&
|
|
21137
|
-
memo.length > 0);
|
|
21138
|
-
}
|
|
21139
|
-
function parseDanalTransferBody(data) {
|
|
21140
|
-
try {
|
|
21141
|
-
const parsed = JSON.parse(data);
|
|
21142
|
-
return isDanalTransferRequestBody(parsed) ? parsed : null;
|
|
21143
|
-
}
|
|
21144
|
-
catch {
|
|
21145
|
-
return null;
|
|
21146
|
-
}
|
|
21108
|
+
return Object.fromEntries(Object.entries(totals).map(([key, total]) => [key, total.toFixed()]));
|
|
21147
21109
|
}
|
|
21148
21110
|
|
|
21149
21111
|
const solanaTokenProgramIdByAddress = {
|
|
@@ -21482,15 +21444,6 @@ function isOnChainTxData(squidData) {
|
|
|
21482
21444
|
function isDepositAddressDirectTransferRoute(squidData) {
|
|
21483
21445
|
return squidData.type === SquidDataType.DepositAddressDirectTransfer;
|
|
21484
21446
|
}
|
|
21485
|
-
/**
|
|
21486
|
-
* Danal keeps SquidDataType.DepositAddressWithMemo, which XRPL/Stellar use for
|
|
21487
|
-
* wallet-signed transactions — so the deposit treatment is gated on the chain,
|
|
21488
|
-
* not just the type
|
|
21489
|
-
*/
|
|
21490
|
-
function isDanalDepositRoute(route) {
|
|
21491
|
-
return (route.params.fromChain === CHAIN_IDS.DANAL_TESTNET &&
|
|
21492
|
-
route.transactionRequest?.type === SquidDataType.DepositAddressWithMemo);
|
|
21493
|
-
}
|
|
21494
21447
|
function getHistoryTransactionId(tx) {
|
|
21495
21448
|
switch (tx.txType) {
|
|
21496
21449
|
case HistoryTxType.SWAP:
|
|
@@ -22015,11 +21968,6 @@ const chainTypeToTrimLength = {
|
|
|
22015
21968
|
start: 3,
|
|
22016
21969
|
end: 3,
|
|
22017
21970
|
},
|
|
22018
|
-
// PCI0180...E77E
|
|
22019
|
-
[ChainType.DANAL]: {
|
|
22020
|
-
start: 7,
|
|
22021
|
-
end: 4,
|
|
22022
|
-
},
|
|
22023
21971
|
};
|
|
22024
21972
|
const truncateWithEllipsis = (value, startLength, endLength) => value.length <= startLength + endLength
|
|
22025
21973
|
? value
|
|
@@ -22068,8 +22016,6 @@ const isWalletAddressValid = (chainData, address) => {
|
|
|
22068
22016
|
return isStellarAddressValid(address);
|
|
22069
22017
|
case ChainType.CANTON:
|
|
22070
22018
|
return isCantonAddressValid(address);
|
|
22071
|
-
case ChainType.DANAL:
|
|
22072
|
-
return isDanalAddressValid(address);
|
|
22073
22019
|
}
|
|
22074
22020
|
};
|
|
22075
22021
|
const redirectToExtensionsStore = (wallet) => {
|
|
@@ -22354,8 +22300,6 @@ const connectByChainType = async (chainType, wallet, defaultChain, params) => {
|
|
|
22354
22300
|
connector,
|
|
22355
22301
|
},
|
|
22356
22302
|
});
|
|
22357
|
-
case ChainType.DANAL:
|
|
22358
|
-
return null;
|
|
22359
22303
|
}
|
|
22360
22304
|
};
|
|
22361
22305
|
const getChainTypesToConnect = (selectedChainTypes, defaultChain, supportedChains) => {
|
|
@@ -22480,6 +22424,8 @@ const cancelConnectWallet = (wallet) => {
|
|
|
22480
22424
|
return;
|
|
22481
22425
|
}
|
|
22482
22426
|
switch (wallet.type) {
|
|
22427
|
+
case ChainType.CANTON:
|
|
22428
|
+
return wallet.connector.cancelConnect?.();
|
|
22483
22429
|
case ChainType.XRPL:
|
|
22484
22430
|
return wallet.connector.cancelConnect?.();
|
|
22485
22431
|
default:
|
|
@@ -23340,7 +23286,7 @@ const handleDestinationAddressOnSwapChange = ({ fromChainId, fromTokenAddress, t
|
|
|
23340
23286
|
});
|
|
23341
23287
|
let destinationTokenAddress = toChainId && !toTokenAddress
|
|
23342
23288
|
? defaultToAddress
|
|
23343
|
-
: toTokenAddress ?? swapRoute?.toTokenAddress;
|
|
23289
|
+
: (toTokenAddress ?? swapRoute?.toTokenAddress);
|
|
23344
23290
|
const lowerCaseDestinationTokenAddress = destinationTokenAddress?.toLowerCase();
|
|
23345
23291
|
if (destinationTokensFiltered.find((t) => t.address === lowerCaseDestinationTokenAddress) === undefined) {
|
|
23346
23292
|
destinationTokenAddress = defaultToAddress;
|
|
@@ -23392,7 +23338,7 @@ const getNewSwapParamsFromInput = ({ inputParams, initialSwapRoute, tokens, chai
|
|
|
23392
23338
|
toTokenAddress === initialSwapRoute?.fromTokenAddress;
|
|
23393
23339
|
const sourceTokenAddress = sameNewDestAndInitialSource || onlySourceChainIdChanged
|
|
23394
23340
|
? defaultFromAddress
|
|
23395
|
-
: fromTokenAddress ?? initialSwapRoute?.fromTokenAddress;
|
|
23341
|
+
: (fromTokenAddress ?? initialSwapRoute?.fromTokenAddress);
|
|
23396
23342
|
let destinationTokenAddress = undefined;
|
|
23397
23343
|
if (!sameNewSourceAndInitialDest) {
|
|
23398
23344
|
if (onlySourceChainIdChanged) {
|
|
@@ -23714,7 +23660,7 @@ const filterViewableTokens = (tokens, config, direction) => {
|
|
|
23714
23660
|
};
|
|
23715
23661
|
const getSecretNetworkBalances = async (chainData, cosmosAddress, squidTokens, keplrTypeWallet) => {
|
|
23716
23662
|
const squidSecretTokens = squidTokens.filter((t) => t.chainId === CHAIN_IDS.SECRET);
|
|
23717
|
-
const { fetchAllSecretBalances } = await import('./secretService
|
|
23663
|
+
const { fetchAllSecretBalances } = await import('./secretService--tJFCkzT.js');
|
|
23718
23664
|
return fetchAllSecretBalances(chainData, cosmosAddress, squidSecretTokens, keplrTypeWallet);
|
|
23719
23665
|
};
|
|
23720
23666
|
function getTokenAssetsKey(token) {
|
|
@@ -25295,8 +25241,7 @@ const searchTokens = (query, tokens, chains) => {
|
|
|
25295
25241
|
.filter((chain) => chain.nativeCurrency.symbol.toLowerCase() === query.toLowerCase() ||
|
|
25296
25242
|
chain.nativeCurrency.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
25297
25243
|
chain.networkName.toLowerCase().includes(query.toLowerCase()) ||
|
|
25298
|
-
|
|
25299
|
-
false))
|
|
25244
|
+
chain.axelarChainName.toLowerCase().includes(query.toLowerCase()))
|
|
25300
25245
|
.map((chain) => tokens.find((token) => token.chainId === chain.chainId &&
|
|
25301
25246
|
token.address.toLowerCase() === nativeEvmTokenAddress))
|
|
25302
25247
|
.filter((token) => token !== undefined);
|
|
@@ -25709,7 +25654,7 @@ const useSquidTokens = (direction) => {
|
|
|
25709
25654
|
config.availableChains,
|
|
25710
25655
|
direction,
|
|
25711
25656
|
]);
|
|
25712
|
-
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens,
|
|
25657
|
+
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens, } = useMemo(() => {
|
|
25713
25658
|
return tokens?.reduce((acc, token) => {
|
|
25714
25659
|
switch (token.type) {
|
|
25715
25660
|
case ChainType.EVM:
|
|
@@ -25736,9 +25681,6 @@ const useSquidTokens = (direction) => {
|
|
|
25736
25681
|
case ChainType.CANTON:
|
|
25737
25682
|
acc.cantonTokens.push(token);
|
|
25738
25683
|
break;
|
|
25739
|
-
case ChainType.DANAL:
|
|
25740
|
-
acc.danalTokens.push(token);
|
|
25741
|
-
break;
|
|
25742
25684
|
}
|
|
25743
25685
|
return acc;
|
|
25744
25686
|
}, {
|
|
@@ -25750,7 +25692,6 @@ const useSquidTokens = (direction) => {
|
|
|
25750
25692
|
xrplTokens: [],
|
|
25751
25693
|
stellarTokens: [],
|
|
25752
25694
|
cantonTokens: [],
|
|
25753
|
-
danalTokens: [],
|
|
25754
25695
|
});
|
|
25755
25696
|
}, [tokens]);
|
|
25756
25697
|
const findToken = useCallback((address, chainId) => {
|
|
@@ -25769,7 +25710,6 @@ const useSquidTokens = (direction) => {
|
|
|
25769
25710
|
xrplTokens,
|
|
25770
25711
|
stellarTokens,
|
|
25771
25712
|
cantonTokens,
|
|
25772
|
-
danalTokens,
|
|
25773
25713
|
};
|
|
25774
25714
|
};
|
|
25775
25715
|
|
|
@@ -26306,7 +26246,6 @@ const chainTypeToRefetchInterval = {
|
|
|
26306
26246
|
[ChainType.XRPL]: 1_000,
|
|
26307
26247
|
[ChainType.STELLAR]: 1_000,
|
|
26308
26248
|
[ChainType.CANTON]: 1_000,
|
|
26309
|
-
[ChainType.DANAL]: 1_000,
|
|
26310
26249
|
};
|
|
26311
26250
|
/**
|
|
26312
26251
|
* Returns the status refetch interval of a Send transaction
|
|
@@ -26573,7 +26512,7 @@ const useSquidChains = (direction) => {
|
|
|
26573
26512
|
const chain = findChain(chainId);
|
|
26574
26513
|
return chain?.chainType;
|
|
26575
26514
|
}, [findChain]);
|
|
26576
|
-
const { evmChains, cosmosChains, suiChains, stellarChains, xrplChains, cantonChains,
|
|
26515
|
+
const { evmChains, cosmosChains, suiChains, stellarChains, xrplChains, cantonChains, } = useMemo(() => {
|
|
26577
26516
|
return chains.reduce((acc, chain) => {
|
|
26578
26517
|
switch (chain.chainType) {
|
|
26579
26518
|
case ChainType.EVM:
|
|
@@ -26594,9 +26533,6 @@ const useSquidChains = (direction) => {
|
|
|
26594
26533
|
case ChainType.CANTON:
|
|
26595
26534
|
acc.cantonChains.push(chain);
|
|
26596
26535
|
break;
|
|
26597
|
-
case ChainType.DANAL:
|
|
26598
|
-
acc.danalChains.push(chain);
|
|
26599
|
-
break;
|
|
26600
26536
|
}
|
|
26601
26537
|
return acc;
|
|
26602
26538
|
}, {
|
|
@@ -26606,7 +26542,6 @@ const useSquidChains = (direction) => {
|
|
|
26606
26542
|
xrplChains: [],
|
|
26607
26543
|
stellarChains: [],
|
|
26608
26544
|
cantonChains: [],
|
|
26609
|
-
danalChains: [],
|
|
26610
26545
|
});
|
|
26611
26546
|
}, [chains]);
|
|
26612
26547
|
const { supportedSourceChains, supportedDestinationChains } = useMemo(() => {
|
|
@@ -26628,7 +26563,6 @@ const useSquidChains = (direction) => {
|
|
|
26628
26563
|
xrplChains,
|
|
26629
26564
|
stellarChains,
|
|
26630
26565
|
cantonChains,
|
|
26631
|
-
danalChains,
|
|
26632
26566
|
getChainType,
|
|
26633
26567
|
findChain,
|
|
26634
26568
|
};
|
|
@@ -26942,7 +26876,343 @@ const useBitcoinContext = () => {
|
|
|
26942
26876
|
return context;
|
|
26943
26877
|
};
|
|
26944
26878
|
|
|
26945
|
-
const
|
|
26879
|
+
const vParty = (party) => ({ party });
|
|
26880
|
+
const vText = (text) => ({ text });
|
|
26881
|
+
const vNumeric = (numeric) => ({ numeric });
|
|
26882
|
+
const vContractId = (contractId) => ({
|
|
26883
|
+
contractId,
|
|
26884
|
+
});
|
|
26885
|
+
const vList = (elements) => ({
|
|
26886
|
+
list: { elements },
|
|
26887
|
+
});
|
|
26888
|
+
const vRecord = (fields) => ({
|
|
26889
|
+
record: { fields: fields.map(([label, value]) => ({ label, value })) },
|
|
26890
|
+
});
|
|
26891
|
+
const vTextMap = (entries) => ({ textMap: { entries } });
|
|
26892
|
+
const vTimestamp = (isoString) => ({
|
|
26893
|
+
timestamp: String(Math.floor(new Date(isoString).getTime() * 1000)),
|
|
26894
|
+
});
|
|
26895
|
+
function isRecord(value) {
|
|
26896
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26897
|
+
}
|
|
26898
|
+
function normalizeChoiceContextValue(value) {
|
|
26899
|
+
if (typeof value === "string")
|
|
26900
|
+
return value;
|
|
26901
|
+
if (!isRecord(value) || typeof value.tag !== "string")
|
|
26902
|
+
return null;
|
|
26903
|
+
switch (value.tag) {
|
|
26904
|
+
case "AV_Text":
|
|
26905
|
+
case "AV_ContractId":
|
|
26906
|
+
return typeof value.value === "string"
|
|
26907
|
+
? { tag: value.tag, value: value.value }
|
|
26908
|
+
: null;
|
|
26909
|
+
case "AV_Bool":
|
|
26910
|
+
return typeof value.value === "boolean"
|
|
26911
|
+
? { tag: value.tag, value: value.value }
|
|
26912
|
+
: null;
|
|
26913
|
+
case "AV_List":
|
|
26914
|
+
if (!Array.isArray(value.value))
|
|
26915
|
+
return null;
|
|
26916
|
+
return {
|
|
26917
|
+
tag: value.tag,
|
|
26918
|
+
value: value.value.flatMap((entry) => {
|
|
26919
|
+
const normalized = normalizeChoiceContextValue(entry);
|
|
26920
|
+
return normalized ? [normalized] : [];
|
|
26921
|
+
}),
|
|
26922
|
+
};
|
|
26923
|
+
default: {
|
|
26924
|
+
const inner = value.value;
|
|
26925
|
+
if (typeof inner === "string" ||
|
|
26926
|
+
typeof inner === "number" ||
|
|
26927
|
+
typeof inner === "boolean" ||
|
|
26928
|
+
inner === null) {
|
|
26929
|
+
return { tag: value.tag, value: inner };
|
|
26930
|
+
}
|
|
26931
|
+
if (Array.isArray(inner)) {
|
|
26932
|
+
return {
|
|
26933
|
+
tag: value.tag,
|
|
26934
|
+
value: inner.flatMap((entry) => {
|
|
26935
|
+
const normalized = normalizeChoiceContextValue(entry);
|
|
26936
|
+
return normalized ? [normalized] : [];
|
|
26937
|
+
}),
|
|
26938
|
+
};
|
|
26939
|
+
}
|
|
26940
|
+
if (!isRecord(inner))
|
|
26941
|
+
return null;
|
|
26942
|
+
const normalizedObject = {};
|
|
26943
|
+
for (const [key, entry] of Object.entries(inner)) {
|
|
26944
|
+
if (typeof entry !== "string" &&
|
|
26945
|
+
typeof entry !== "number" &&
|
|
26946
|
+
typeof entry !== "boolean" &&
|
|
26947
|
+
entry !== null) {
|
|
26948
|
+
return null;
|
|
26949
|
+
}
|
|
26950
|
+
normalizedObject[key] = entry;
|
|
26951
|
+
}
|
|
26952
|
+
return { tag: value.tag, value: normalizedObject };
|
|
26953
|
+
}
|
|
26954
|
+
}
|
|
26955
|
+
}
|
|
26956
|
+
function stringifyChoiceContextValue(value) {
|
|
26957
|
+
return typeof value === "string" ? value : String(value);
|
|
26958
|
+
}
|
|
26959
|
+
function choiceContextValueToAnyValue(value) {
|
|
26960
|
+
if (typeof value === "string") {
|
|
26961
|
+
return {
|
|
26962
|
+
variant: { constructor: "AV_Text", value: vText(value) },
|
|
26963
|
+
};
|
|
26964
|
+
}
|
|
26965
|
+
switch (value.tag) {
|
|
26966
|
+
case "AV_ContractId":
|
|
26967
|
+
if (typeof value.value !== "string") {
|
|
26968
|
+
throw new TypeError("AV_ContractId values must be strings");
|
|
26969
|
+
}
|
|
26970
|
+
return {
|
|
26971
|
+
variant: { constructor: value.tag, value: vContractId(value.value) },
|
|
26972
|
+
};
|
|
26973
|
+
case "AV_Bool":
|
|
26974
|
+
if (typeof value.value !== "boolean") {
|
|
26975
|
+
throw new TypeError("AV_Bool values must be booleans");
|
|
26976
|
+
}
|
|
26977
|
+
return {
|
|
26978
|
+
variant: { constructor: value.tag, value: { bool: value.value } },
|
|
26979
|
+
};
|
|
26980
|
+
case "AV_List":
|
|
26981
|
+
if (!Array.isArray(value.value)) {
|
|
26982
|
+
throw new TypeError("AV_List values must be arrays");
|
|
26983
|
+
}
|
|
26984
|
+
return {
|
|
26985
|
+
variant: {
|
|
26986
|
+
constructor: value.tag,
|
|
26987
|
+
value: vList(value.value.map(choiceContextValueToAnyValue)),
|
|
26988
|
+
},
|
|
26989
|
+
};
|
|
26990
|
+
default:
|
|
26991
|
+
return {
|
|
26992
|
+
variant: {
|
|
26993
|
+
constructor: value.tag,
|
|
26994
|
+
value: vText(stringifyChoiceContextValue(value.value)),
|
|
26995
|
+
},
|
|
26996
|
+
};
|
|
26997
|
+
}
|
|
26998
|
+
}
|
|
26999
|
+
|
|
27000
|
+
/** Metadata key used by Splice token-standard transfers to carry our Squid orderhash memo. */
|
|
27001
|
+
const CANTON_MEMO_KEY = "splice.lfdecentralizedtrust.org/reason";
|
|
27002
|
+
/** Interface id for the transfer factory contract returned by the token registry. */
|
|
27003
|
+
const TRANSFER_FACTORY_INTERFACE_ID = "#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory";
|
|
27004
|
+
/** Choice exercised on the transfer factory to create the token-standard transfer. */
|
|
27005
|
+
const TRANSFER_FACTORY_CHOICE = "TransferFactory_Transfer";
|
|
27006
|
+
/** CIP-056 (non-Amulet) tokens expose a registrar parameterized by admin party. */
|
|
27007
|
+
const CIP056_REGISTRAR_BASE = "https://api.utilities.digitalasset.com/api/token-standard/v0/registrars";
|
|
27008
|
+
const TRANSFER_REQUESTED_AT_SKEW_MS = 60_000;
|
|
27009
|
+
const TRANSFER_EXECUTE_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
27010
|
+
function resolveCantonRegistryUrl(instrument) {
|
|
27011
|
+
if (instrument.id === CANTON_AMULET_INSTRUMENT_ID) {
|
|
27012
|
+
return CANTON_AMULET_REGISTRY_URL;
|
|
27013
|
+
}
|
|
27014
|
+
return `${CIP056_REGISTRAR_BASE}/${encodeURIComponent(instrument.admin)}`;
|
|
27015
|
+
}
|
|
27016
|
+
/**
|
|
27017
|
+
* Build the plain JSON arguments expected by the registry transfer-factory
|
|
27018
|
+
* endpoint before it adds choice context and disclosed contracts.
|
|
27019
|
+
*/
|
|
27020
|
+
function buildTransferChoiceArgs({ sender, receiver, amount, instrument, inputHoldingCids, memo, nowMs, }) {
|
|
27021
|
+
return {
|
|
27022
|
+
expectedAdmin: instrument.admin,
|
|
27023
|
+
transfer: {
|
|
27024
|
+
sender,
|
|
27025
|
+
receiver,
|
|
27026
|
+
amount,
|
|
27027
|
+
instrumentId: { admin: instrument.admin, id: instrument.id },
|
|
27028
|
+
requestedAt: new Date(nowMs - TRANSFER_REQUESTED_AT_SKEW_MS).toISOString(),
|
|
27029
|
+
executeBefore: new Date(nowMs + TRANSFER_EXECUTE_WINDOW_MS).toISOString(),
|
|
27030
|
+
inputHoldingCids,
|
|
27031
|
+
meta: { values: { [CANTON_MEMO_KEY]: memo } },
|
|
27032
|
+
},
|
|
27033
|
+
extraArgs: { context: { values: {} }, meta: { values: {} } },
|
|
27034
|
+
};
|
|
27035
|
+
}
|
|
27036
|
+
/**
|
|
27037
|
+
* Canton's Holding contracts follow Bitcoin-style UTXOs model.
|
|
27038
|
+
*
|
|
27039
|
+
* Pick Holding contract ids covering `amount` for the instrument: prefer a
|
|
27040
|
+
* single exact-amount holding, else accumulate largest-first.
|
|
27041
|
+
*/
|
|
27042
|
+
function selectInputHoldingCids(holdings, instrument, amount) {
|
|
27043
|
+
const spendableHoldings = holdings.filter((h) => !h.locked &&
|
|
27044
|
+
h.instrument.admin === instrument.admin &&
|
|
27045
|
+
h.instrument.id === instrument.id);
|
|
27046
|
+
const target = new BigNumber(amount);
|
|
27047
|
+
const exact = spendableHoldings.find((h) => new BigNumber(h.amount).eq(target));
|
|
27048
|
+
if (exact)
|
|
27049
|
+
return [exact.contractId];
|
|
27050
|
+
const sorted = [...spendableHoldings].sort((a, b) => new BigNumber(b.amount).comparedTo(new BigNumber(a.amount)));
|
|
27051
|
+
const selected = [];
|
|
27052
|
+
let sum = new BigNumber(0);
|
|
27053
|
+
for (const holding of sorted) {
|
|
27054
|
+
selected.push(holding.contractId);
|
|
27055
|
+
sum = sum.plus(holding.amount);
|
|
27056
|
+
if (sum.gte(target))
|
|
27057
|
+
break;
|
|
27058
|
+
}
|
|
27059
|
+
if (sum.lt(target)) {
|
|
27060
|
+
throw new Error("Insufficient Canton holdings to cover transfer amount");
|
|
27061
|
+
}
|
|
27062
|
+
return selected;
|
|
27063
|
+
}
|
|
27064
|
+
async function fetchInputHoldingCids(connector, party, instrument, amount) {
|
|
27065
|
+
const ledgerEnd = await connector.ledgerApi({
|
|
27066
|
+
requestMethod: "get",
|
|
27067
|
+
resource: "/v2/state/ledger-end",
|
|
27068
|
+
});
|
|
27069
|
+
const activeContracts = await connector.ledgerApi({
|
|
27070
|
+
requestMethod: "post",
|
|
27071
|
+
resource: "/v2/state/active-contracts",
|
|
27072
|
+
body: buildHoldingsAcsRequestBody(party, String(ledgerEnd.offset)),
|
|
27073
|
+
});
|
|
27074
|
+
return selectInputHoldingCids(parseCantonHoldings(activeContracts), instrument, amount);
|
|
27075
|
+
}
|
|
27076
|
+
/**
|
|
27077
|
+
* Ask the token registry for the transfer factory contract and extra context
|
|
27078
|
+
* required to exercise its transfer choice.
|
|
27079
|
+
*/
|
|
27080
|
+
async function fetchTransferFactory(registryUrl, choiceArgs) {
|
|
27081
|
+
const response = await fetch(`${registryUrl}/registry/transfer-instruction/v1/transfer-factory`, {
|
|
27082
|
+
method: "POST",
|
|
27083
|
+
headers: { "Content-Type": "application/json" },
|
|
27084
|
+
body: JSON.stringify({
|
|
27085
|
+
choiceArguments: choiceArgs,
|
|
27086
|
+
excludeDebugFields: true,
|
|
27087
|
+
}),
|
|
27088
|
+
});
|
|
27089
|
+
if (!response.ok) {
|
|
27090
|
+
throw new Error(`Canton transfer-factory request failed (${response.status})`);
|
|
27091
|
+
}
|
|
27092
|
+
return response.json();
|
|
27093
|
+
}
|
|
27094
|
+
/**
|
|
27095
|
+
* Convert transfer choice args into strict Daml Value JSON for the wallet's
|
|
27096
|
+
* prepareExecute ExerciseCommand.
|
|
27097
|
+
*/
|
|
27098
|
+
function buildPrepareChoiceArgument(choiceArgs) {
|
|
27099
|
+
const contextValues = Object.entries(choiceArgs.extraArgs.context?.values ?? {})
|
|
27100
|
+
.map(([key, value]) => {
|
|
27101
|
+
const normalized = normalizeChoiceContextValue(value);
|
|
27102
|
+
return normalized
|
|
27103
|
+
? { key, value: choiceContextValueToAnyValue(normalized) }
|
|
27104
|
+
: undefined;
|
|
27105
|
+
})
|
|
27106
|
+
.filter((entry) => Boolean(entry));
|
|
27107
|
+
return vRecord([
|
|
27108
|
+
["expectedAdmin", vParty(choiceArgs.expectedAdmin)],
|
|
27109
|
+
[
|
|
27110
|
+
"transfer",
|
|
27111
|
+
vRecord([
|
|
27112
|
+
["sender", vParty(choiceArgs.transfer.sender)],
|
|
27113
|
+
["receiver", vParty(choiceArgs.transfer.receiver)],
|
|
27114
|
+
["amount", vNumeric(choiceArgs.transfer.amount)],
|
|
27115
|
+
[
|
|
27116
|
+
"instrumentId",
|
|
27117
|
+
vRecord([
|
|
27118
|
+
["admin", vParty(choiceArgs.transfer.instrumentId.admin)],
|
|
27119
|
+
["id", vText(choiceArgs.transfer.instrumentId.id)],
|
|
27120
|
+
]),
|
|
27121
|
+
],
|
|
27122
|
+
["requestedAt", vTimestamp(choiceArgs.transfer.requestedAt)],
|
|
27123
|
+
["executeBefore", vTimestamp(choiceArgs.transfer.executeBefore)],
|
|
27124
|
+
[
|
|
27125
|
+
"inputHoldingCids",
|
|
27126
|
+
vList(choiceArgs.transfer.inputHoldingCids.map(vContractId)),
|
|
27127
|
+
],
|
|
27128
|
+
[
|
|
27129
|
+
"meta",
|
|
27130
|
+
vRecord([
|
|
27131
|
+
[
|
|
27132
|
+
"values",
|
|
27133
|
+
vTextMap(Object.entries(choiceArgs.transfer.meta.values).map(([key, value]) => ({ key, value: vText(value) }))),
|
|
27134
|
+
],
|
|
27135
|
+
]),
|
|
27136
|
+
],
|
|
27137
|
+
]),
|
|
27138
|
+
],
|
|
27139
|
+
[
|
|
27140
|
+
"extraArgs",
|
|
27141
|
+
vRecord([
|
|
27142
|
+
["context", vRecord([["values", vTextMap(contextValues)]])],
|
|
27143
|
+
["meta", vRecord([["values", vTextMap([])]])],
|
|
27144
|
+
]),
|
|
27145
|
+
],
|
|
27146
|
+
]);
|
|
27147
|
+
}
|
|
27148
|
+
/**
|
|
27149
|
+
* Convert a registry disclosed contract into the wallet submission shape,
|
|
27150
|
+
* dropping debug fields and entries without a usable createdEventBlob.
|
|
27151
|
+
*/
|
|
27152
|
+
function toDisclosedContract(raw) {
|
|
27153
|
+
if (typeof raw.createdEventBlob !== "string")
|
|
27154
|
+
return null;
|
|
27155
|
+
return {
|
|
27156
|
+
createdEventBlob: raw.createdEventBlob,
|
|
27157
|
+
contractId: typeof raw.contractId === "string" ? raw.contractId : undefined,
|
|
27158
|
+
synchronizerId: typeof raw.synchronizerId === "string" ? raw.synchronizerId : undefined,
|
|
27159
|
+
};
|
|
27160
|
+
}
|
|
27161
|
+
/**
|
|
27162
|
+
* Build a Splice token-standard transfer of `amount` of `token` from `sender`
|
|
27163
|
+
* to `receiver`, carrying `memo`, ready to hand to a wallet's prepareExecute.
|
|
27164
|
+
* Returns the full prepareExecute params (commandId + commands + actAs +
|
|
27165
|
+
* disclosedContracts).
|
|
27166
|
+
*/
|
|
27167
|
+
async function buildCantonTransfer(connector, { sender, receiver, amount, token, memo, nowMs, }) {
|
|
27168
|
+
if (!token.originalAddress) {
|
|
27169
|
+
throw new Error("Canton token originalAddress is required");
|
|
27170
|
+
}
|
|
27171
|
+
const instrument = parseCantonInstrument(token.originalAddress);
|
|
27172
|
+
if (!instrument) {
|
|
27173
|
+
throw new Error(`Invalid Canton token address: ${token.originalAddress}`);
|
|
27174
|
+
}
|
|
27175
|
+
const registryUrl = resolveCantonRegistryUrl(instrument);
|
|
27176
|
+
const inputHoldingCids = await fetchInputHoldingCids(connector, sender, instrument, amount);
|
|
27177
|
+
const choiceArgs = buildTransferChoiceArgs({
|
|
27178
|
+
sender,
|
|
27179
|
+
receiver,
|
|
27180
|
+
amount,
|
|
27181
|
+
instrument,
|
|
27182
|
+
inputHoldingCids,
|
|
27183
|
+
memo,
|
|
27184
|
+
nowMs,
|
|
27185
|
+
});
|
|
27186
|
+
const factory = await fetchTransferFactory(registryUrl, choiceArgs);
|
|
27187
|
+
const choiceContextData = factory.choiceContext.choiceContextData;
|
|
27188
|
+
if (!choiceContextData) {
|
|
27189
|
+
throw new Error("Transfer factory choice context is missing");
|
|
27190
|
+
}
|
|
27191
|
+
choiceArgs.extraArgs.context = choiceContextData;
|
|
27192
|
+
const disclosedContracts = (factory.choiceContext?.disclosedContracts ?? []).flatMap((raw) => {
|
|
27193
|
+
const disclosedContract = toDisclosedContract(raw);
|
|
27194
|
+
return disclosedContract ? [disclosedContract] : [];
|
|
27195
|
+
});
|
|
27196
|
+
// Command ID: client-supplied identifier used to deduplicate submissions
|
|
27197
|
+
const commandId = getCantonCommandId(memo);
|
|
27198
|
+
return {
|
|
27199
|
+
commandId,
|
|
27200
|
+
commands: [
|
|
27201
|
+
{
|
|
27202
|
+
ExerciseCommand: {
|
|
27203
|
+
templateId: TRANSFER_FACTORY_INTERFACE_ID,
|
|
27204
|
+
contractId: factory.factoryId,
|
|
27205
|
+
choice: TRANSFER_FACTORY_CHOICE,
|
|
27206
|
+
choiceArgument: buildPrepareChoiceArgument(choiceArgs),
|
|
27207
|
+
},
|
|
27208
|
+
},
|
|
27209
|
+
],
|
|
27210
|
+
actAs: [sender],
|
|
27211
|
+
disclosedContracts,
|
|
27212
|
+
};
|
|
27213
|
+
}
|
|
27214
|
+
|
|
27215
|
+
const REQUEST_TIMEOUT_MS$1 = 60_000;
|
|
26946
27216
|
function pickAddress(accounts) {
|
|
26947
27217
|
return (accounts.find((a) => a.primary) ?? accounts[0])?.partyId;
|
|
26948
27218
|
}
|
|
@@ -26951,6 +27221,7 @@ function pickAddress(accounts) {
|
|
|
26951
27221
|
* (bound to its announce `target`) and exposes the lifecycle methods.
|
|
26952
27222
|
*/
|
|
26953
27223
|
class CantonBrowserConnector {
|
|
27224
|
+
kind = "cip103";
|
|
26954
27225
|
providerId;
|
|
26955
27226
|
name;
|
|
26956
27227
|
target;
|
|
@@ -26965,7 +27236,7 @@ class CantonBrowserConnector {
|
|
|
26965
27236
|
client ??
|
|
26966
27237
|
createCantonClient(new WindowTransport(win, {
|
|
26967
27238
|
target: this.target,
|
|
26968
|
-
timeout: REQUEST_TIMEOUT_MS,
|
|
27239
|
+
timeout: REQUEST_TIMEOUT_MS$1,
|
|
26969
27240
|
}));
|
|
26970
27241
|
}
|
|
26971
27242
|
get accounts() {
|
|
@@ -27011,6 +27282,28 @@ class CantonBrowserConnector {
|
|
|
27011
27282
|
this._accounts = await this.client.listAccounts();
|
|
27012
27283
|
return this._accounts;
|
|
27013
27284
|
}
|
|
27285
|
+
async getBalances(partyId) {
|
|
27286
|
+
const { offset } = await this.ledgerApi({
|
|
27287
|
+
requestMethod: "get",
|
|
27288
|
+
resource: "/v2/state/ledger-end",
|
|
27289
|
+
});
|
|
27290
|
+
const contracts = await this.ledgerApi({
|
|
27291
|
+
requestMethod: "post",
|
|
27292
|
+
resource: "/v2/state/active-contracts",
|
|
27293
|
+
body: buildHoldingsAcsRequestBody(partyId, String(offset)),
|
|
27294
|
+
});
|
|
27295
|
+
return groupCantonHoldingBalances(contracts);
|
|
27296
|
+
}
|
|
27297
|
+
async transfer(params) {
|
|
27298
|
+
const payload = await buildCantonTransfer(this, params);
|
|
27299
|
+
const result = await this.prepareExecuteAndWait(payload);
|
|
27300
|
+
const updateId = result.tx?.payload?.updateId ?? "";
|
|
27301
|
+
return {
|
|
27302
|
+
status: "completed",
|
|
27303
|
+
commandId: getCantonCommandId(params.memo),
|
|
27304
|
+
updateId,
|
|
27305
|
+
};
|
|
27306
|
+
}
|
|
27014
27307
|
on(event, handler) {
|
|
27015
27308
|
this.listeners[event].add(handler);
|
|
27016
27309
|
}
|
|
@@ -27117,6 +27410,313 @@ function discoverInjectedCantonWallets(win) {
|
|
|
27117
27410
|
});
|
|
27118
27411
|
}
|
|
27119
27412
|
|
|
27413
|
+
class CantonConnectionCancelledError extends Error {
|
|
27414
|
+
code = 4001;
|
|
27415
|
+
constructor() {
|
|
27416
|
+
super("Wallet connection cancelled");
|
|
27417
|
+
}
|
|
27418
|
+
}
|
|
27419
|
+
|
|
27420
|
+
const REQUEST_TIMEOUT_MS = 5 * 60_000;
|
|
27421
|
+
const OVERLAY_ID = "loop-sdk-connect-overlay";
|
|
27422
|
+
class LoopSubmissionUnknownError extends Error {
|
|
27423
|
+
constructor(cause) {
|
|
27424
|
+
super("Loop transfer outcome is unknown. Check Loop before trying again.", {
|
|
27425
|
+
cause,
|
|
27426
|
+
});
|
|
27427
|
+
}
|
|
27428
|
+
}
|
|
27429
|
+
function parseLoopTransferUpdate(value, commandId) {
|
|
27430
|
+
if (!value || typeof value !== "object")
|
|
27431
|
+
return undefined;
|
|
27432
|
+
const response = value;
|
|
27433
|
+
if (response.command_id !== commandId)
|
|
27434
|
+
return undefined;
|
|
27435
|
+
if (response.status === "failed" || response.error) {
|
|
27436
|
+
const error = response.error;
|
|
27437
|
+
return {
|
|
27438
|
+
status: "failed",
|
|
27439
|
+
commandId,
|
|
27440
|
+
message: typeof error?.error_message === "string"
|
|
27441
|
+
? error.error_message
|
|
27442
|
+
: "Loop transaction failed",
|
|
27443
|
+
};
|
|
27444
|
+
}
|
|
27445
|
+
if (typeof response.update_id === "string" && response.update_id) {
|
|
27446
|
+
return { status: "completed", commandId, updateId: response.update_id };
|
|
27447
|
+
}
|
|
27448
|
+
return undefined;
|
|
27449
|
+
}
|
|
27450
|
+
class LoopCantonConnector {
|
|
27451
|
+
loadSdk;
|
|
27452
|
+
kind = "loop";
|
|
27453
|
+
providerId = "canton-loop";
|
|
27454
|
+
name = "Loop";
|
|
27455
|
+
target = undefined;
|
|
27456
|
+
module;
|
|
27457
|
+
initialization;
|
|
27458
|
+
provider;
|
|
27459
|
+
pendingConnection;
|
|
27460
|
+
connectionStart = Promise.resolve();
|
|
27461
|
+
transfers = new Map();
|
|
27462
|
+
listeners = { connect: new Set(), disconnect: new Set() };
|
|
27463
|
+
constructor(loadSdk = () => import('@fivenorth/loop-sdk')) {
|
|
27464
|
+
this.loadSdk = loadSdk;
|
|
27465
|
+
}
|
|
27466
|
+
get accounts() {
|
|
27467
|
+
return this.provider
|
|
27468
|
+
? [{ partyId: this.provider.party_id, primary: true }]
|
|
27469
|
+
: [];
|
|
27470
|
+
}
|
|
27471
|
+
get address() {
|
|
27472
|
+
return this.provider?.party_id;
|
|
27473
|
+
}
|
|
27474
|
+
connect() {
|
|
27475
|
+
return this.startConnection(true);
|
|
27476
|
+
}
|
|
27477
|
+
autoConnect() {
|
|
27478
|
+
return this.startConnection(false);
|
|
27479
|
+
}
|
|
27480
|
+
cancelConnect() {
|
|
27481
|
+
if (!this.pendingConnection)
|
|
27482
|
+
return;
|
|
27483
|
+
this.rejectConnection(new CantonConnectionCancelledError());
|
|
27484
|
+
this.module?.loop.logout();
|
|
27485
|
+
}
|
|
27486
|
+
async disconnect() {
|
|
27487
|
+
this.cancelConnect();
|
|
27488
|
+
this.module?.loop.logout();
|
|
27489
|
+
this.provider = undefined;
|
|
27490
|
+
this.emit("disconnect");
|
|
27491
|
+
}
|
|
27492
|
+
async getAccounts() {
|
|
27493
|
+
const provider = this.requireProvider();
|
|
27494
|
+
try {
|
|
27495
|
+
const account = await provider.getAccount();
|
|
27496
|
+
if (account.party_id !== provider.party_id) {
|
|
27497
|
+
await this.disconnect();
|
|
27498
|
+
throw new Error("Loop account changed. Connect again.");
|
|
27499
|
+
}
|
|
27500
|
+
return this.accounts;
|
|
27501
|
+
}
|
|
27502
|
+
catch (error) {
|
|
27503
|
+
if (this.module && error instanceof this.module.UnauthorizedError) {
|
|
27504
|
+
await this.disconnect();
|
|
27505
|
+
}
|
|
27506
|
+
throw error;
|
|
27507
|
+
}
|
|
27508
|
+
}
|
|
27509
|
+
async getBalances(partyId) {
|
|
27510
|
+
const provider = this.requireProvider();
|
|
27511
|
+
if (partyId !== provider.party_id) {
|
|
27512
|
+
throw new Error("Loop balances require the connected account");
|
|
27513
|
+
}
|
|
27514
|
+
const holdings = await provider.getHolding();
|
|
27515
|
+
return sumCantonHoldingBalances(holdings.map((holding) => {
|
|
27516
|
+
const amount = new BigNumber(holding.total_unlocked_coin);
|
|
27517
|
+
if (!amount.isFinite() || amount.isNegative()) {
|
|
27518
|
+
throw new Error("Loop returned an invalid balance");
|
|
27519
|
+
}
|
|
27520
|
+
return {
|
|
27521
|
+
instrument: holding.instrument_id,
|
|
27522
|
+
amount: holding.total_unlocked_coin,
|
|
27523
|
+
};
|
|
27524
|
+
}));
|
|
27525
|
+
}
|
|
27526
|
+
transfer(params) {
|
|
27527
|
+
const commandId = getCantonCommandId(params.memo);
|
|
27528
|
+
const existing = this.transfers.get(commandId);
|
|
27529
|
+
if (existing)
|
|
27530
|
+
return existing;
|
|
27531
|
+
const request = this.submitTransfer(params, commandId).catch((error) => {
|
|
27532
|
+
if (!(error instanceof LoopSubmissionUnknownError))
|
|
27533
|
+
this.transfers.delete(commandId);
|
|
27534
|
+
throw error;
|
|
27535
|
+
});
|
|
27536
|
+
this.transfers.set(commandId, request);
|
|
27537
|
+
return request;
|
|
27538
|
+
}
|
|
27539
|
+
on(event, handler) {
|
|
27540
|
+
this.listeners[event].add(handler);
|
|
27541
|
+
}
|
|
27542
|
+
off(event, handler) {
|
|
27543
|
+
this.listeners[event].delete(handler);
|
|
27544
|
+
}
|
|
27545
|
+
emit(event, ...args) {
|
|
27546
|
+
this.listeners[event].forEach((handler) => handler(...args));
|
|
27547
|
+
}
|
|
27548
|
+
initialize() {
|
|
27549
|
+
if (!this.initialization) {
|
|
27550
|
+
this.initialization = this.loadSdk()
|
|
27551
|
+
.then((module) => {
|
|
27552
|
+
module.loop.init({
|
|
27553
|
+
appName: SQUID_METADATA.name,
|
|
27554
|
+
network: "mainnet",
|
|
27555
|
+
onAccept: (provider) => this.acceptConnection(provider),
|
|
27556
|
+
onReject: () => {
|
|
27557
|
+
this.rejectConnection(new CantonConnectionCancelledError());
|
|
27558
|
+
this.provider = undefined;
|
|
27559
|
+
this.emit("disconnect");
|
|
27560
|
+
},
|
|
27561
|
+
});
|
|
27562
|
+
this.module = module;
|
|
27563
|
+
})
|
|
27564
|
+
.catch((error) => {
|
|
27565
|
+
this.initialization = undefined;
|
|
27566
|
+
throw error;
|
|
27567
|
+
});
|
|
27568
|
+
}
|
|
27569
|
+
return this.initialization;
|
|
27570
|
+
}
|
|
27571
|
+
startConnection(interactive) {
|
|
27572
|
+
if (this.pendingConnection)
|
|
27573
|
+
return this.pendingConnection.promise;
|
|
27574
|
+
let resolve;
|
|
27575
|
+
let reject;
|
|
27576
|
+
const promise = new Promise((accept, decline) => {
|
|
27577
|
+
resolve = accept;
|
|
27578
|
+
reject = decline;
|
|
27579
|
+
});
|
|
27580
|
+
const timer = setTimeout(() => {
|
|
27581
|
+
this.rejectConnection(new Error("Loop connection timed out"));
|
|
27582
|
+
this.module?.loop.logout();
|
|
27583
|
+
}, REQUEST_TIMEOUT_MS);
|
|
27584
|
+
let observer;
|
|
27585
|
+
const pending = {
|
|
27586
|
+
promise,
|
|
27587
|
+
resolve,
|
|
27588
|
+
reject,
|
|
27589
|
+
cleanup: () => {
|
|
27590
|
+
clearTimeout(timer);
|
|
27591
|
+
observer?.disconnect();
|
|
27592
|
+
},
|
|
27593
|
+
};
|
|
27594
|
+
this.pendingConnection = pending;
|
|
27595
|
+
this.connectionStart = this.connectionStart
|
|
27596
|
+
.then(async () => {
|
|
27597
|
+
await this.initialize();
|
|
27598
|
+
if (this.pendingConnection !== pending)
|
|
27599
|
+
return;
|
|
27600
|
+
if (interactive && typeof MutationObserver !== "undefined") {
|
|
27601
|
+
let sawOverlay = false;
|
|
27602
|
+
observer = new MutationObserver(() => {
|
|
27603
|
+
const overlay = document.getElementById(OVERLAY_ID);
|
|
27604
|
+
if (overlay)
|
|
27605
|
+
sawOverlay = true;
|
|
27606
|
+
if (sawOverlay && !overlay && this.pendingConnection === pending) {
|
|
27607
|
+
this.cancelConnect();
|
|
27608
|
+
}
|
|
27609
|
+
});
|
|
27610
|
+
observer.observe(document.body, { childList: true });
|
|
27611
|
+
}
|
|
27612
|
+
const sdk = this.module.loop;
|
|
27613
|
+
if (interactive)
|
|
27614
|
+
await sdk.connect();
|
|
27615
|
+
else
|
|
27616
|
+
await sdk.autoConnect();
|
|
27617
|
+
if (!interactive && this.pendingConnection === pending) {
|
|
27618
|
+
this.rejectConnection(new Error("No saved Loop connection"));
|
|
27619
|
+
}
|
|
27620
|
+
else if (interactive &&
|
|
27621
|
+
this.pendingConnection !== pending &&
|
|
27622
|
+
!this.provider) {
|
|
27623
|
+
sdk.logout();
|
|
27624
|
+
}
|
|
27625
|
+
})
|
|
27626
|
+
.catch((error) => {
|
|
27627
|
+
if (this.pendingConnection === pending)
|
|
27628
|
+
this.rejectConnection(error);
|
|
27629
|
+
});
|
|
27630
|
+
return promise;
|
|
27631
|
+
}
|
|
27632
|
+
acceptConnection(provider) {
|
|
27633
|
+
const pending = this.pendingConnection;
|
|
27634
|
+
if (!pending)
|
|
27635
|
+
return;
|
|
27636
|
+
if (!provider.party_id) {
|
|
27637
|
+
this.rejectConnection(new Error("Loop returned no account"));
|
|
27638
|
+
return;
|
|
27639
|
+
}
|
|
27640
|
+
this.provider = provider;
|
|
27641
|
+
this.pendingConnection = undefined;
|
|
27642
|
+
pending.cleanup();
|
|
27643
|
+
pending.resolve({ address: provider.party_id, accounts: this.accounts });
|
|
27644
|
+
this.emit("connect", provider.party_id);
|
|
27645
|
+
}
|
|
27646
|
+
rejectConnection(error) {
|
|
27647
|
+
const pending = this.pendingConnection;
|
|
27648
|
+
if (!pending)
|
|
27649
|
+
return;
|
|
27650
|
+
this.pendingConnection = undefined;
|
|
27651
|
+
pending.cleanup();
|
|
27652
|
+
pending.reject(error);
|
|
27653
|
+
}
|
|
27654
|
+
requireProvider() {
|
|
27655
|
+
if (!this.provider)
|
|
27656
|
+
throw new Error("Loop wallet is not connected");
|
|
27657
|
+
return this.provider;
|
|
27658
|
+
}
|
|
27659
|
+
async submitTransfer(params, commandId) {
|
|
27660
|
+
const provider = this.requireProvider();
|
|
27661
|
+
if (params.sender !== provider.party_id)
|
|
27662
|
+
throw new Error("Loop account does not match the sender");
|
|
27663
|
+
const instrument = params.token.originalAddress
|
|
27664
|
+
? parseCantonInstrument(params.token.originalAddress)
|
|
27665
|
+
: null;
|
|
27666
|
+
if (!instrument)
|
|
27667
|
+
throw new Error("Canton token originalAddress is required");
|
|
27668
|
+
if (!new BigNumber(params.amount).isFinite() ||
|
|
27669
|
+
!new BigNumber(params.amount).isPositive()) {
|
|
27670
|
+
throw new Error("Canton transfer amount must be positive");
|
|
27671
|
+
}
|
|
27672
|
+
const payload = await provider.connection.prepareTransfer(provider.getAuthToken(), {
|
|
27673
|
+
recipient: params.receiver,
|
|
27674
|
+
amount: params.amount,
|
|
27675
|
+
instrument: {
|
|
27676
|
+
instrument_admin: instrument.admin,
|
|
27677
|
+
instrument_id: instrument.id,
|
|
27678
|
+
},
|
|
27679
|
+
memo: params.memo,
|
|
27680
|
+
requested_at: new Date(params.nowMs - 60_000).toISOString(),
|
|
27681
|
+
execute_before: new Date(params.nowMs + 24 * 60 * 60_000).toISOString(),
|
|
27682
|
+
});
|
|
27683
|
+
if (this.provider !== provider)
|
|
27684
|
+
throw new Error("Loop account disconnected before submission");
|
|
27685
|
+
let response;
|
|
27686
|
+
let timer;
|
|
27687
|
+
try {
|
|
27688
|
+
const submission = { ...payload, commandId };
|
|
27689
|
+
const submitted = provider.submitAndWaitForTransaction(submission, {
|
|
27690
|
+
requestTimeout: REQUEST_TIMEOUT_MS,
|
|
27691
|
+
});
|
|
27692
|
+
response = await Promise.race([
|
|
27693
|
+
submitted,
|
|
27694
|
+
new Promise((_, reject) => {
|
|
27695
|
+
timer = setTimeout(() => reject(new Error("Loop submission timed out")), REQUEST_TIMEOUT_MS);
|
|
27696
|
+
}),
|
|
27697
|
+
]);
|
|
27698
|
+
}
|
|
27699
|
+
catch (error) {
|
|
27700
|
+
if (this.module && error instanceof this.module.RejectRequestError) {
|
|
27701
|
+
throw Object.assign(new Error("Loop transaction rejected"), {
|
|
27702
|
+
code: 4001,
|
|
27703
|
+
});
|
|
27704
|
+
}
|
|
27705
|
+
throw new LoopSubmissionUnknownError(error);
|
|
27706
|
+
}
|
|
27707
|
+
finally {
|
|
27708
|
+
clearTimeout(timer);
|
|
27709
|
+
}
|
|
27710
|
+
const update = parseLoopTransferUpdate(response, commandId);
|
|
27711
|
+
if (update?.status === "failed")
|
|
27712
|
+
throw new Error(update.message);
|
|
27713
|
+
if (!update)
|
|
27714
|
+
throw new LoopSubmissionUnknownError();
|
|
27715
|
+
return update;
|
|
27716
|
+
}
|
|
27717
|
+
}
|
|
27718
|
+
const loopCantonConnector = new LoopCantonConnector();
|
|
27719
|
+
|
|
27120
27720
|
/**
|
|
27121
27721
|
* Discovers Canton browser-extension wallets and maps each to a
|
|
27122
27722
|
* {@link CantonWallet}: CIP-103 announce-protocol wallets (on mount and on
|
|
@@ -27163,7 +27763,19 @@ function useCantonWallets() {
|
|
|
27163
27763
|
return;
|
|
27164
27764
|
const announced = (await discoverCantonProviders()).map(toAnnouncedWallet);
|
|
27165
27765
|
const injected = discoverInjectedCantonWallets().map(toInjectedWallet);
|
|
27166
|
-
|
|
27766
|
+
const loopWallet = {
|
|
27767
|
+
...formatCantonWallet({
|
|
27768
|
+
connectorId: "canton-loop",
|
|
27769
|
+
name: "Loop",
|
|
27770
|
+
icon: "https://raw.githubusercontent.com/0xsquid/assets/main/images/master/wallets/canton-loop.svg",
|
|
27771
|
+
connector: loopCantonConnector,
|
|
27772
|
+
}),
|
|
27773
|
+
links: { website: "https://cantonloop.com" },
|
|
27774
|
+
isInstalled: () => false,
|
|
27775
|
+
skipInstallCheck: true,
|
|
27776
|
+
isMobile: true,
|
|
27777
|
+
};
|
|
27778
|
+
setWallets(mergeInjectedCantonWallets(announced, [...injected, loopWallet]));
|
|
27167
27779
|
}, [toAnnouncedWallet, toInjectedWallet]);
|
|
27168
27780
|
useEffect(() => {
|
|
27169
27781
|
refresh();
|
|
@@ -27194,7 +27806,9 @@ function useCanton() {
|
|
|
27194
27806
|
// Try to reconnect silently first (existing session, no popup)
|
|
27195
27807
|
result = await wallet.connector.autoConnect();
|
|
27196
27808
|
}
|
|
27197
|
-
catch {
|
|
27809
|
+
catch (error) {
|
|
27810
|
+
if (error instanceof CantonConnectionCancelledError)
|
|
27811
|
+
throw error;
|
|
27198
27812
|
// Otherwise prompt the wallet to connect
|
|
27199
27813
|
result = await wallet.connector.connect();
|
|
27200
27814
|
}
|
|
@@ -27216,8 +27830,7 @@ function useCanton() {
|
|
|
27216
27830
|
wallet: recentCantonWallet,
|
|
27217
27831
|
address,
|
|
27218
27832
|
});
|
|
27219
|
-
|
|
27220
|
-
}, [wallets.map((w) => w.connectorId), recentCantonWalletId]);
|
|
27833
|
+
}, [wallets, recentCantonWalletId, setConnectedWallet]);
|
|
27221
27834
|
useEffect(() => {
|
|
27222
27835
|
const wallet = connectedCantonWallet.wallet;
|
|
27223
27836
|
if (!wallet) {
|
|
@@ -27474,7 +28087,7 @@ function useStellarWallets() {
|
|
|
27474
28087
|
try {
|
|
27475
28088
|
const { allowAllModules: initializeAllModules } = await import('@creit.tech/stellar-wallets-kit');
|
|
27476
28089
|
const { LedgerModule } = await import('@creit.tech/stellar-wallets-kit/modules/ledger.module.mjs');
|
|
27477
|
-
const { formatStellarWallet } = await import('./stellarService.client-
|
|
28090
|
+
const { formatStellarWallet } = await import('./stellarService.client-DAxgO3m3.js');
|
|
27478
28091
|
const modules = [...initializeAllModules(), new LedgerModule()];
|
|
27479
28092
|
const promises = modules.map(async (module) => {
|
|
27480
28093
|
const isAvailable = await module.isAvailable();
|
|
@@ -28053,6 +28666,11 @@ const useWallet = () => {
|
|
|
28053
28666
|
}
|
|
28054
28667
|
}
|
|
28055
28668
|
catch (error) {
|
|
28669
|
+
const connecting = useWalletStore.getState().connectingWalletState;
|
|
28670
|
+
if (connecting.wallet !== wallet ||
|
|
28671
|
+
connecting.status !== ConnectingWalletStatus.CONNECTING) {
|
|
28672
|
+
return false;
|
|
28673
|
+
}
|
|
28056
28674
|
console.error("Failed to connect wallet", error);
|
|
28057
28675
|
walletConnectionError(wallet, error);
|
|
28058
28676
|
return false;
|
|
@@ -28284,8 +28902,6 @@ const useMultiChainWallet = (chain) => {
|
|
|
28284
28902
|
chainType: chain.chainType,
|
|
28285
28903
|
}),
|
|
28286
28904
|
};
|
|
28287
|
-
case ChainType.DANAL:
|
|
28288
|
-
return {};
|
|
28289
28905
|
}
|
|
28290
28906
|
}, [
|
|
28291
28907
|
chain?.chainType,
|
|
@@ -29124,8 +29740,6 @@ async function createClient(chain) {
|
|
|
29124
29740
|
return new StellarRpcClient(chain.rpc);
|
|
29125
29741
|
case ChainType.CANTON:
|
|
29126
29742
|
return null;
|
|
29127
|
-
case ChainType.DANAL:
|
|
29128
|
-
return null;
|
|
29129
29743
|
}
|
|
29130
29744
|
}
|
|
29131
29745
|
|
|
@@ -30140,8 +30754,7 @@ const getAllStellarTokensBalance = async (userAddress, stellarTokens, stellarCha
|
|
|
30140
30754
|
};
|
|
30141
30755
|
/**
|
|
30142
30756
|
* Canton balances are private: they can only be read with a wallet-authorized
|
|
30143
|
-
*
|
|
30144
|
-
* party). So balance is fetchable only when a Canton wallet is connected, and
|
|
30757
|
+
* session. Balance is fetchable only when a Canton wallet is connected, and
|
|
30145
30758
|
* only for that wallet's party — there's no address-only read.
|
|
30146
30759
|
*/
|
|
30147
30760
|
const getCantonTokenBalance = async (userAddress, token, cantonConnector) => {
|
|
@@ -30150,30 +30763,14 @@ const getCantonTokenBalance = async (userAddress, token, cantonConnector) => {
|
|
|
30150
30763
|
const instrument = parseCantonInstrument(token.originalAddress);
|
|
30151
30764
|
if (!instrument)
|
|
30152
30765
|
return "0";
|
|
30153
|
-
const
|
|
30154
|
-
const activeContracts = await cantonConnector.ledgerApi({
|
|
30155
|
-
requestMethod: "post",
|
|
30156
|
-
resource: "/v2/state/active-contracts",
|
|
30157
|
-
body: buildHoldingsAcsRequestBody(userAddress, String(ledgerEnd.offset)),
|
|
30158
|
-
});
|
|
30159
|
-
const grouped = groupCantonHoldingBalances(activeContracts);
|
|
30766
|
+
const grouped = await cantonConnector.getBalances(userAddress);
|
|
30160
30767
|
return grouped[cantonInstrumentKey(instrument)] ?? "0";
|
|
30161
30768
|
};
|
|
30162
30769
|
/**
|
|
30163
|
-
* Fetch balances for all Canton tokens
|
|
30164
|
-
* private, so this requires a connected wallet's authorized `ledgerApi`.
|
|
30770
|
+
* Fetch balances for all Canton tokens through the connected wallet.
|
|
30165
30771
|
*/
|
|
30166
30772
|
async function getAllCantonTokensBalance({ connector, partyId, tokens, }) {
|
|
30167
|
-
const
|
|
30168
|
-
requestMethod: "get",
|
|
30169
|
-
resource: "/v2/state/ledger-end",
|
|
30170
|
-
});
|
|
30171
|
-
const activeContracts = await connector.ledgerApi({
|
|
30172
|
-
requestMethod: "post",
|
|
30173
|
-
resource: "/v2/state/active-contracts",
|
|
30174
|
-
body: buildHoldingsAcsRequestBody(partyId, String(ledgerEnd.offset)),
|
|
30175
|
-
});
|
|
30176
|
-
const grouped = groupCantonHoldingBalances(activeContracts);
|
|
30773
|
+
const grouped = await connector.getBalances(partyId);
|
|
30177
30774
|
return tokens.map((token) => {
|
|
30178
30775
|
const instrument = token.originalAddress
|
|
30179
30776
|
? parseCantonInstrument(token.originalAddress)
|
|
@@ -30392,7 +30989,7 @@ const useCantonBalance = ({ chain, token, userAddress, enabled = true, refreshIn
|
|
|
30392
30989
|
};
|
|
30393
30990
|
|
|
30394
30991
|
function useNativeTokenForChain(chain) {
|
|
30395
|
-
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens,
|
|
30992
|
+
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens, } = useSquidTokens();
|
|
30396
30993
|
const getTokensForChainType = () => {
|
|
30397
30994
|
if (!chain?.chainType)
|
|
30398
30995
|
return [];
|
|
@@ -30413,8 +31010,6 @@ function useNativeTokenForChain(chain) {
|
|
|
30413
31010
|
return stellarTokens;
|
|
30414
31011
|
case ChainType.CANTON:
|
|
30415
31012
|
return cantonTokens;
|
|
30416
|
-
case ChainType.DANAL:
|
|
30417
|
-
return danalTokens;
|
|
30418
31013
|
}
|
|
30419
31014
|
};
|
|
30420
31015
|
const nativeTokenForChainType = useMemo(() => {
|
|
@@ -30675,9 +31270,6 @@ const useNativeBalance = (chain) => {
|
|
|
30675
31270
|
case ChainType.CANTON:
|
|
30676
31271
|
balance = nativeCantonBalance;
|
|
30677
31272
|
break;
|
|
30678
|
-
case ChainType.DANAL:
|
|
30679
|
-
balance = undefined;
|
|
30680
|
-
break;
|
|
30681
31273
|
}
|
|
30682
31274
|
const balanceFormatted = !!balance
|
|
30683
31275
|
? formatBNToReadable(balance.value, balance.decimals)
|
|
@@ -30717,8 +31309,6 @@ const useNativeBalance = (chain) => {
|
|
|
30717
31309
|
return isStellarLoading;
|
|
30718
31310
|
case ChainType.CANTON:
|
|
30719
31311
|
return isCantonLoading;
|
|
30720
|
-
case ChainType.DANAL:
|
|
30721
|
-
return false;
|
|
30722
31312
|
}
|
|
30723
31313
|
}, [
|
|
30724
31314
|
chain?.chainType,
|
|
@@ -30944,7 +31534,7 @@ function hederaWalletConnect(parameters) {
|
|
|
30944
31534
|
const optionalChains = config.chains.map((x) => x.id);
|
|
30945
31535
|
if (!optionalChains.length)
|
|
30946
31536
|
return;
|
|
30947
|
-
const { EthereumProvider } = await import('./index.es
|
|
31537
|
+
const { EthereumProvider } = await import('./index.es-09IsADsA.js');
|
|
30948
31538
|
const rawProvider = await EthereumProvider.init({
|
|
30949
31539
|
...restParameters,
|
|
30950
31540
|
disableProviderPing: true,
|
|
@@ -31178,8 +31768,6 @@ const useSigner = ({ chain }) => {
|
|
|
31178
31768
|
return isStellarSignerReady;
|
|
31179
31769
|
case ChainType.CANTON:
|
|
31180
31770
|
return isCantonSignerReady;
|
|
31181
|
-
case ChainType.DANAL:
|
|
31182
|
-
return false;
|
|
31183
31771
|
}
|
|
31184
31772
|
}, [
|
|
31185
31773
|
chain?.chainType,
|
|
@@ -31632,12 +32220,8 @@ const useSendTransactionStore = create((set, get) => ({
|
|
|
31632
32220
|
const useDepositAddressStore = create((set) => ({
|
|
31633
32221
|
deposit: null,
|
|
31634
32222
|
selectedPaymentMethod: "connectedWallet",
|
|
31635
|
-
userDepositTxId: null,
|
|
31636
32223
|
setDeposit: (data) => {
|
|
31637
|
-
set({ deposit: data
|
|
31638
|
-
},
|
|
31639
|
-
setUserDepositTxId: (txId) => {
|
|
31640
|
-
set({ userDepositTxId: txId });
|
|
32224
|
+
set({ deposit: data });
|
|
31641
32225
|
},
|
|
31642
32226
|
setPaymentMethod: (method) => {
|
|
31643
32227
|
set({ selectedPaymentMethod: method });
|
|
@@ -31649,7 +32233,7 @@ function useDepositAddress(squidRoute) {
|
|
|
31649
32233
|
selectedPaymentMethod: state.selectedPaymentMethod,
|
|
31650
32234
|
depositAddress: state.deposit?.depositAddress,
|
|
31651
32235
|
}));
|
|
31652
|
-
const { setDeposit, setPaymentMethod, deposit
|
|
32236
|
+
const { setDeposit, setPaymentMethod, deposit } = useDepositAddressStore();
|
|
31653
32237
|
const { squid } = useSquidStore();
|
|
31654
32238
|
const { fromChain } = useSwap();
|
|
31655
32239
|
const isAvailableAsPaymentMethod = useMemo(() => {
|
|
@@ -31659,13 +32243,11 @@ function useDepositAddress(squidRoute) {
|
|
|
31659
32243
|
CHAIN_IDS.BITCOIN,
|
|
31660
32244
|
CHAIN_IDS.SOLANA,
|
|
31661
32245
|
CHAIN_IDS.CANTON,
|
|
31662
|
-
CHAIN_IDS.DANAL_TESTNET,
|
|
31663
32246
|
];
|
|
31664
32247
|
return chainsSupportingDepositAddress.includes(fromChain.chainId);
|
|
31665
32248
|
}, [fromChain?.chainId]);
|
|
31666
|
-
const routeProvidesDirectDepositAddress =
|
|
31667
|
-
isDepositAddressDirectTransferRoute(squidRoute.transactionRequest)
|
|
31668
|
-
(!!squidRoute && isDanalDepositRoute(squidRoute));
|
|
32249
|
+
const routeProvidesDirectDepositAddress = !!squidRoute?.transactionRequest &&
|
|
32250
|
+
isDepositAddressDirectTransferRoute(squidRoute.transactionRequest);
|
|
31669
32251
|
const swapWillGenerateDepositAddress = isChainflipDepositRoute(squidRoute) || routeProvidesDirectDepositAddress;
|
|
31670
32252
|
const paymentMethod = isAvailableAsPaymentMethod
|
|
31671
32253
|
? selectedPaymentMethod
|
|
@@ -31691,19 +32273,6 @@ function useDepositAddress(squidRoute) {
|
|
|
31691
32273
|
});
|
|
31692
32274
|
return;
|
|
31693
32275
|
}
|
|
31694
|
-
else if (isDanalDepositRoute(route)) {
|
|
31695
|
-
const transferBody = parseDanalTransferBody(route.transactionRequest.data);
|
|
31696
|
-
if (!transferBody) {
|
|
31697
|
-
throw new Error("Could not parse Danal transfer request body");
|
|
31698
|
-
}
|
|
31699
|
-
setDeposit({
|
|
31700
|
-
amount: transferBody.amount,
|
|
31701
|
-
depositAddress: route.transactionRequest.target,
|
|
31702
|
-
statusTrackingId: transferBody.memo,
|
|
31703
|
-
memo: transferBody.memo,
|
|
31704
|
-
});
|
|
31705
|
-
return;
|
|
31706
|
-
}
|
|
31707
32276
|
else if (isDepositAddressDirectTransferRoute(route.transactionRequest)) {
|
|
31708
32277
|
// Canton case
|
|
31709
32278
|
const orderHash = route.transactionRequest.data;
|
|
@@ -31726,8 +32295,6 @@ function useDepositAddress(squidRoute) {
|
|
|
31726
32295
|
depositAddress,
|
|
31727
32296
|
closeDepositChannel,
|
|
31728
32297
|
depositData: deposit,
|
|
31729
|
-
userDepositTxId,
|
|
31730
|
-
setUserDepositTxId,
|
|
31731
32298
|
};
|
|
31732
32299
|
}
|
|
31733
32300
|
|
|
@@ -31755,7 +32322,7 @@ const useUrlSwapParams = () => {
|
|
|
31755
32322
|
|
|
31756
32323
|
const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, queryOptions, }) => {
|
|
31757
32324
|
const { evmChains, cosmosChains, suiChains, xrplChains, stellarChains } = useSquidChains(direction);
|
|
31758
|
-
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens,
|
|
32325
|
+
const { evmTokens, cosmosTokens, solanaTokens, bitcoinTokens, suiTokens, xrplTokens, stellarTokens, cantonTokens, } = useSquidTokens(direction);
|
|
31759
32326
|
const { keplrTypeWallet } = useCosmosContext();
|
|
31760
32327
|
const cantonConnector = useWalletStore((store) => store.connectedWalletsByChainType[ChainType.CANTON]?.wallet?.connector);
|
|
31761
32328
|
const placeholderData = useMemo(() => {
|
|
@@ -31768,7 +32335,6 @@ const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, qu
|
|
|
31768
32335
|
[ChainType.XRPL]: xrplTokens.map((t) => ({ ...t, balance: "0" })),
|
|
31769
32336
|
[ChainType.STELLAR]: stellarTokens.map((t) => ({ ...t, balance: "0" })),
|
|
31770
32337
|
[ChainType.CANTON]: cantonTokens.map((t) => ({ ...t, balance: "0" })),
|
|
31771
|
-
[ChainType.DANAL]: danalTokens.map((t) => ({ ...t, balance: "0" })),
|
|
31772
32338
|
};
|
|
31773
32339
|
if (!chainType) {
|
|
31774
32340
|
// Return all tokens with zero balance
|
|
@@ -31791,7 +32357,6 @@ const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, qu
|
|
|
31791
32357
|
xrplTokens,
|
|
31792
32358
|
stellarTokens,
|
|
31793
32359
|
cantonTokens,
|
|
31794
|
-
danalTokens,
|
|
31795
32360
|
]);
|
|
31796
32361
|
const isQueryEnabled = useMemo(() => {
|
|
31797
32362
|
// Respect the queryOptions.enabled override if provided
|
|
@@ -31816,8 +32381,6 @@ const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, qu
|
|
|
31816
32381
|
return stellarTokens.length > 0;
|
|
31817
32382
|
case ChainType.CANTON:
|
|
31818
32383
|
return cantonTokens.length > 0;
|
|
31819
|
-
case ChainType.DANAL:
|
|
31820
|
-
return danalTokens.length > 0;
|
|
31821
32384
|
}
|
|
31822
32385
|
}, [
|
|
31823
32386
|
chainType,
|
|
@@ -31831,7 +32394,6 @@ const useAllTokensWithBalanceForChainType = ({ chainType, address, direction, qu
|
|
|
31831
32394
|
xrplTokens.length,
|
|
31832
32395
|
stellarTokens.length,
|
|
31833
32396
|
cantonTokens.length,
|
|
31834
|
-
danalTokens.length,
|
|
31835
32397
|
]);
|
|
31836
32398
|
const query = useQuery(keys().allTokensBalance(address, chainType, direction), async () => {
|
|
31837
32399
|
// Return zero balances if no address
|
|
@@ -31956,12 +32518,6 @@ const useAllConnectedWalletBalances = ({ direction, queryOptions = {
|
|
|
31956
32518
|
direction,
|
|
31957
32519
|
queryOptions,
|
|
31958
32520
|
});
|
|
31959
|
-
const danalBalancesQuery = useAllTokensWithBalanceForChainType({
|
|
31960
|
-
chainType: ChainType.DANAL,
|
|
31961
|
-
address: connectedAddresses?.[ChainType.DANAL],
|
|
31962
|
-
direction,
|
|
31963
|
-
queryOptions,
|
|
31964
|
-
});
|
|
31965
32521
|
// Create a map of chain type to balance query results
|
|
31966
32522
|
const balanceQueries = useMemo(() => ({
|
|
31967
32523
|
[ChainType.EVM]: evmBalancesQuery,
|
|
@@ -31972,7 +32528,6 @@ const useAllConnectedWalletBalances = ({ direction, queryOptions = {
|
|
|
31972
32528
|
[ChainType.XRPL]: xrplBalancesQuery,
|
|
31973
32529
|
[ChainType.STELLAR]: stellarBalancesQuery,
|
|
31974
32530
|
[ChainType.CANTON]: cantonBalancesQuery,
|
|
31975
|
-
[ChainType.DANAL]: danalBalancesQuery,
|
|
31976
32531
|
}), [
|
|
31977
32532
|
evmBalancesQuery,
|
|
31978
32533
|
cosmosBalancesQuery,
|
|
@@ -31982,7 +32537,6 @@ const useAllConnectedWalletBalances = ({ direction, queryOptions = {
|
|
|
31982
32537
|
xrplBalancesQuery,
|
|
31983
32538
|
stellarBalancesQuery,
|
|
31984
32539
|
cantonBalancesQuery,
|
|
31985
|
-
danalBalancesQuery,
|
|
31986
32540
|
]);
|
|
31987
32541
|
// Combine all tokens from different chains
|
|
31988
32542
|
const allTokens = useMemo(() => Object.values(balanceQueries).flatMap((query) => query.data?.tokens ?? []), [balanceQueries]);
|
|
@@ -32090,8 +32644,6 @@ const useMultiChainBalance = ({ chain, token, userAddress, enabled = true, }) =>
|
|
|
32090
32644
|
return stellarBalance;
|
|
32091
32645
|
case ChainType.CANTON:
|
|
32092
32646
|
return cantonBalance;
|
|
32093
|
-
case ChainType.DANAL:
|
|
32094
|
-
return "0";
|
|
32095
32647
|
}
|
|
32096
32648
|
}, [
|
|
32097
32649
|
chain?.chainType,
|
|
@@ -33201,9 +33753,8 @@ function useSendTransaction({ to, amount, token, chain, }) {
|
|
|
33201
33753
|
chain,
|
|
33202
33754
|
});
|
|
33203
33755
|
break;
|
|
33204
|
-
case ChainType.CANTON:
|
|
33205
|
-
|
|
33206
|
-
throw new Error(`Send not implemented for chain type ${chain.chainType}`);
|
|
33756
|
+
case ChainType.CANTON: {
|
|
33757
|
+
throw new Error("Not implemented");
|
|
33207
33758
|
}
|
|
33208
33759
|
}
|
|
33209
33760
|
return {
|
|
@@ -33409,8 +33960,7 @@ async function getSendTransactionStatus({ chain, txHash, }) {
|
|
|
33409
33960
|
chain,
|
|
33410
33961
|
});
|
|
33411
33962
|
case ChainType.CANTON:
|
|
33412
|
-
|
|
33413
|
-
throw new Error(`Send not implemented for chain type ${chain.chainType}`);
|
|
33963
|
+
throw new Error("Not implemented");
|
|
33414
33964
|
}
|
|
33415
33965
|
}
|
|
33416
33966
|
|
|
@@ -37031,342 +37581,6 @@ coin.DecProto = {
|
|
|
37031
37581
|
|
|
37032
37582
|
} (tx));
|
|
37033
37583
|
|
|
37034
|
-
const vParty = (party) => ({ party });
|
|
37035
|
-
const vText = (text) => ({ text });
|
|
37036
|
-
const vNumeric = (numeric) => ({ numeric });
|
|
37037
|
-
const vContractId = (contractId) => ({
|
|
37038
|
-
contractId,
|
|
37039
|
-
});
|
|
37040
|
-
const vList = (elements) => ({
|
|
37041
|
-
list: { elements },
|
|
37042
|
-
});
|
|
37043
|
-
const vRecord = (fields) => ({
|
|
37044
|
-
record: { fields: fields.map(([label, value]) => ({ label, value })) },
|
|
37045
|
-
});
|
|
37046
|
-
const vTextMap = (entries) => ({ textMap: { entries } });
|
|
37047
|
-
const vTimestamp = (isoString) => ({
|
|
37048
|
-
timestamp: String(Math.floor(new Date(isoString).getTime() * 1000)),
|
|
37049
|
-
});
|
|
37050
|
-
function isRecord(value) {
|
|
37051
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37052
|
-
}
|
|
37053
|
-
function normalizeChoiceContextValue(value) {
|
|
37054
|
-
if (typeof value === "string")
|
|
37055
|
-
return value;
|
|
37056
|
-
if (!isRecord(value) || typeof value.tag !== "string")
|
|
37057
|
-
return null;
|
|
37058
|
-
switch (value.tag) {
|
|
37059
|
-
case "AV_Text":
|
|
37060
|
-
case "AV_ContractId":
|
|
37061
|
-
return typeof value.value === "string"
|
|
37062
|
-
? { tag: value.tag, value: value.value }
|
|
37063
|
-
: null;
|
|
37064
|
-
case "AV_Bool":
|
|
37065
|
-
return typeof value.value === "boolean"
|
|
37066
|
-
? { tag: value.tag, value: value.value }
|
|
37067
|
-
: null;
|
|
37068
|
-
case "AV_List":
|
|
37069
|
-
if (!Array.isArray(value.value))
|
|
37070
|
-
return null;
|
|
37071
|
-
return {
|
|
37072
|
-
tag: value.tag,
|
|
37073
|
-
value: value.value.flatMap((entry) => {
|
|
37074
|
-
const normalized = normalizeChoiceContextValue(entry);
|
|
37075
|
-
return normalized ? [normalized] : [];
|
|
37076
|
-
}),
|
|
37077
|
-
};
|
|
37078
|
-
default: {
|
|
37079
|
-
const inner = value.value;
|
|
37080
|
-
if (typeof inner === "string" ||
|
|
37081
|
-
typeof inner === "number" ||
|
|
37082
|
-
typeof inner === "boolean" ||
|
|
37083
|
-
inner === null) {
|
|
37084
|
-
return { tag: value.tag, value: inner };
|
|
37085
|
-
}
|
|
37086
|
-
if (Array.isArray(inner)) {
|
|
37087
|
-
return {
|
|
37088
|
-
tag: value.tag,
|
|
37089
|
-
value: inner.flatMap((entry) => {
|
|
37090
|
-
const normalized = normalizeChoiceContextValue(entry);
|
|
37091
|
-
return normalized ? [normalized] : [];
|
|
37092
|
-
}),
|
|
37093
|
-
};
|
|
37094
|
-
}
|
|
37095
|
-
if (!isRecord(inner))
|
|
37096
|
-
return null;
|
|
37097
|
-
const normalizedObject = {};
|
|
37098
|
-
for (const [key, entry] of Object.entries(inner)) {
|
|
37099
|
-
if (typeof entry !== "string" &&
|
|
37100
|
-
typeof entry !== "number" &&
|
|
37101
|
-
typeof entry !== "boolean" &&
|
|
37102
|
-
entry !== null) {
|
|
37103
|
-
return null;
|
|
37104
|
-
}
|
|
37105
|
-
normalizedObject[key] = entry;
|
|
37106
|
-
}
|
|
37107
|
-
return { tag: value.tag, value: normalizedObject };
|
|
37108
|
-
}
|
|
37109
|
-
}
|
|
37110
|
-
}
|
|
37111
|
-
function stringifyChoiceContextValue(value) {
|
|
37112
|
-
return typeof value === "string" ? value : String(value);
|
|
37113
|
-
}
|
|
37114
|
-
function choiceContextValueToAnyValue(value) {
|
|
37115
|
-
if (typeof value === "string") {
|
|
37116
|
-
return {
|
|
37117
|
-
variant: { constructor: "AV_Text", value: vText(value) },
|
|
37118
|
-
};
|
|
37119
|
-
}
|
|
37120
|
-
switch (value.tag) {
|
|
37121
|
-
case "AV_ContractId":
|
|
37122
|
-
if (typeof value.value !== "string") {
|
|
37123
|
-
throw new TypeError("AV_ContractId values must be strings");
|
|
37124
|
-
}
|
|
37125
|
-
return {
|
|
37126
|
-
variant: { constructor: value.tag, value: vContractId(value.value) },
|
|
37127
|
-
};
|
|
37128
|
-
case "AV_Bool":
|
|
37129
|
-
if (typeof value.value !== "boolean") {
|
|
37130
|
-
throw new TypeError("AV_Bool values must be booleans");
|
|
37131
|
-
}
|
|
37132
|
-
return {
|
|
37133
|
-
variant: { constructor: value.tag, value: { bool: value.value } },
|
|
37134
|
-
};
|
|
37135
|
-
case "AV_List":
|
|
37136
|
-
if (!Array.isArray(value.value)) {
|
|
37137
|
-
throw new TypeError("AV_List values must be arrays");
|
|
37138
|
-
}
|
|
37139
|
-
return {
|
|
37140
|
-
variant: {
|
|
37141
|
-
constructor: value.tag,
|
|
37142
|
-
value: vList(value.value.map(choiceContextValueToAnyValue)),
|
|
37143
|
-
},
|
|
37144
|
-
};
|
|
37145
|
-
default:
|
|
37146
|
-
return {
|
|
37147
|
-
variant: {
|
|
37148
|
-
constructor: value.tag,
|
|
37149
|
-
value: vText(stringifyChoiceContextValue(value.value)),
|
|
37150
|
-
},
|
|
37151
|
-
};
|
|
37152
|
-
}
|
|
37153
|
-
}
|
|
37154
|
-
|
|
37155
|
-
/** Metadata key used by Splice token-standard transfers to carry our Squid orderhash memo. */
|
|
37156
|
-
const CANTON_MEMO_KEY = "splice.lfdecentralizedtrust.org/reason";
|
|
37157
|
-
/** Interface id for the transfer factory contract returned by the token registry. */
|
|
37158
|
-
const TRANSFER_FACTORY_INTERFACE_ID = "#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory";
|
|
37159
|
-
/** Choice exercised on the transfer factory to create the token-standard transfer. */
|
|
37160
|
-
const TRANSFER_FACTORY_CHOICE = "TransferFactory_Transfer";
|
|
37161
|
-
/** CIP-056 (non-Amulet) tokens expose a registrar parameterized by admin party. */
|
|
37162
|
-
const CIP056_REGISTRAR_BASE = "https://api.utilities.digitalasset.com/api/token-standard/v0/registrars";
|
|
37163
|
-
const TRANSFER_REQUESTED_AT_SKEW_MS = 60_000;
|
|
37164
|
-
const TRANSFER_EXECUTE_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
37165
|
-
function resolveCantonRegistryUrl(instrument) {
|
|
37166
|
-
if (instrument.id === CANTON_AMULET_INSTRUMENT_ID) {
|
|
37167
|
-
return CANTON_AMULET_REGISTRY_URL;
|
|
37168
|
-
}
|
|
37169
|
-
return `${CIP056_REGISTRAR_BASE}/${encodeURIComponent(instrument.admin)}`;
|
|
37170
|
-
}
|
|
37171
|
-
/**
|
|
37172
|
-
* Build the plain JSON arguments expected by the registry transfer-factory
|
|
37173
|
-
* endpoint before it adds choice context and disclosed contracts.
|
|
37174
|
-
*/
|
|
37175
|
-
function buildTransferChoiceArgs({ sender, receiver, amount, instrument, inputHoldingCids, memo, nowMs, }) {
|
|
37176
|
-
return {
|
|
37177
|
-
expectedAdmin: instrument.admin,
|
|
37178
|
-
transfer: {
|
|
37179
|
-
sender,
|
|
37180
|
-
receiver,
|
|
37181
|
-
amount,
|
|
37182
|
-
instrumentId: { admin: instrument.admin, id: instrument.id },
|
|
37183
|
-
requestedAt: new Date(nowMs - TRANSFER_REQUESTED_AT_SKEW_MS).toISOString(),
|
|
37184
|
-
executeBefore: new Date(nowMs + TRANSFER_EXECUTE_WINDOW_MS).toISOString(),
|
|
37185
|
-
inputHoldingCids,
|
|
37186
|
-
meta: { values: { [CANTON_MEMO_KEY]: memo } },
|
|
37187
|
-
},
|
|
37188
|
-
extraArgs: { context: { values: {} }, meta: { values: {} } },
|
|
37189
|
-
};
|
|
37190
|
-
}
|
|
37191
|
-
/**
|
|
37192
|
-
* Canton's Holding contracts follow Bitcoin-style UTXOs model.
|
|
37193
|
-
*
|
|
37194
|
-
* Pick Holding contract ids covering `amount` for the instrument: prefer a
|
|
37195
|
-
* single exact-amount holding, else accumulate largest-first.
|
|
37196
|
-
*/
|
|
37197
|
-
function selectInputHoldingCids(holdings, instrument, amount) {
|
|
37198
|
-
const spendableHoldings = holdings.filter((h) => !h.locked &&
|
|
37199
|
-
h.instrument.admin === instrument.admin &&
|
|
37200
|
-
h.instrument.id === instrument.id);
|
|
37201
|
-
const target = new BigNumber(amount);
|
|
37202
|
-
const exact = spendableHoldings.find((h) => new BigNumber(h.amount).eq(target));
|
|
37203
|
-
if (exact)
|
|
37204
|
-
return [exact.contractId];
|
|
37205
|
-
const sorted = [...spendableHoldings].sort((a, b) => new BigNumber(b.amount).comparedTo(new BigNumber(a.amount)));
|
|
37206
|
-
const selected = [];
|
|
37207
|
-
let sum = new BigNumber(0);
|
|
37208
|
-
for (const holding of sorted) {
|
|
37209
|
-
selected.push(holding.contractId);
|
|
37210
|
-
sum = sum.plus(holding.amount);
|
|
37211
|
-
if (sum.gte(target))
|
|
37212
|
-
break;
|
|
37213
|
-
}
|
|
37214
|
-
if (sum.lt(target)) {
|
|
37215
|
-
throw new Error("Insufficient Canton holdings to cover transfer amount");
|
|
37216
|
-
}
|
|
37217
|
-
return selected;
|
|
37218
|
-
}
|
|
37219
|
-
async function fetchInputHoldingCids(connector, party, instrument, amount) {
|
|
37220
|
-
const ledgerEnd = await connector.ledgerApi({
|
|
37221
|
-
requestMethod: "get",
|
|
37222
|
-
resource: "/v2/state/ledger-end",
|
|
37223
|
-
});
|
|
37224
|
-
const activeContracts = await connector.ledgerApi({
|
|
37225
|
-
requestMethod: "post",
|
|
37226
|
-
resource: "/v2/state/active-contracts",
|
|
37227
|
-
body: buildHoldingsAcsRequestBody(party, String(ledgerEnd.offset)),
|
|
37228
|
-
});
|
|
37229
|
-
return selectInputHoldingCids(parseCantonHoldings(activeContracts), instrument, amount);
|
|
37230
|
-
}
|
|
37231
|
-
/**
|
|
37232
|
-
* Ask the token registry for the transfer factory contract and extra context
|
|
37233
|
-
* required to exercise its transfer choice.
|
|
37234
|
-
*/
|
|
37235
|
-
async function fetchTransferFactory(registryUrl, choiceArgs) {
|
|
37236
|
-
const response = await fetch(`${registryUrl}/registry/transfer-instruction/v1/transfer-factory`, {
|
|
37237
|
-
method: "POST",
|
|
37238
|
-
headers: { "Content-Type": "application/json" },
|
|
37239
|
-
body: JSON.stringify({
|
|
37240
|
-
choiceArguments: choiceArgs,
|
|
37241
|
-
excludeDebugFields: true,
|
|
37242
|
-
}),
|
|
37243
|
-
});
|
|
37244
|
-
if (!response.ok) {
|
|
37245
|
-
throw new Error(`Canton transfer-factory request failed (${response.status})`);
|
|
37246
|
-
}
|
|
37247
|
-
return response.json();
|
|
37248
|
-
}
|
|
37249
|
-
/**
|
|
37250
|
-
* Convert transfer choice args into strict Daml Value JSON for the wallet's
|
|
37251
|
-
* prepareExecute ExerciseCommand.
|
|
37252
|
-
*/
|
|
37253
|
-
function buildPrepareChoiceArgument(choiceArgs) {
|
|
37254
|
-
const contextValues = Object.entries(choiceArgs.extraArgs.context?.values ?? {})
|
|
37255
|
-
.map(([key, value]) => {
|
|
37256
|
-
const normalized = normalizeChoiceContextValue(value);
|
|
37257
|
-
return normalized
|
|
37258
|
-
? { key, value: choiceContextValueToAnyValue(normalized) }
|
|
37259
|
-
: undefined;
|
|
37260
|
-
})
|
|
37261
|
-
.filter((entry) => Boolean(entry));
|
|
37262
|
-
return vRecord([
|
|
37263
|
-
["expectedAdmin", vParty(choiceArgs.expectedAdmin)],
|
|
37264
|
-
[
|
|
37265
|
-
"transfer",
|
|
37266
|
-
vRecord([
|
|
37267
|
-
["sender", vParty(choiceArgs.transfer.sender)],
|
|
37268
|
-
["receiver", vParty(choiceArgs.transfer.receiver)],
|
|
37269
|
-
["amount", vNumeric(choiceArgs.transfer.amount)],
|
|
37270
|
-
[
|
|
37271
|
-
"instrumentId",
|
|
37272
|
-
vRecord([
|
|
37273
|
-
["admin", vParty(choiceArgs.transfer.instrumentId.admin)],
|
|
37274
|
-
["id", vText(choiceArgs.transfer.instrumentId.id)],
|
|
37275
|
-
]),
|
|
37276
|
-
],
|
|
37277
|
-
["requestedAt", vTimestamp(choiceArgs.transfer.requestedAt)],
|
|
37278
|
-
["executeBefore", vTimestamp(choiceArgs.transfer.executeBefore)],
|
|
37279
|
-
[
|
|
37280
|
-
"inputHoldingCids",
|
|
37281
|
-
vList(choiceArgs.transfer.inputHoldingCids.map(vContractId)),
|
|
37282
|
-
],
|
|
37283
|
-
[
|
|
37284
|
-
"meta",
|
|
37285
|
-
vRecord([
|
|
37286
|
-
[
|
|
37287
|
-
"values",
|
|
37288
|
-
vTextMap(Object.entries(choiceArgs.transfer.meta.values).map(([key, value]) => ({ key, value: vText(value) }))),
|
|
37289
|
-
],
|
|
37290
|
-
]),
|
|
37291
|
-
],
|
|
37292
|
-
]),
|
|
37293
|
-
],
|
|
37294
|
-
[
|
|
37295
|
-
"extraArgs",
|
|
37296
|
-
vRecord([
|
|
37297
|
-
["context", vRecord([["values", vTextMap(contextValues)]])],
|
|
37298
|
-
["meta", vRecord([["values", vTextMap([])]])],
|
|
37299
|
-
]),
|
|
37300
|
-
],
|
|
37301
|
-
]);
|
|
37302
|
-
}
|
|
37303
|
-
/**
|
|
37304
|
-
* Convert a registry disclosed contract into the wallet submission shape,
|
|
37305
|
-
* dropping debug fields and entries without a usable createdEventBlob.
|
|
37306
|
-
*/
|
|
37307
|
-
function toDisclosedContract(raw) {
|
|
37308
|
-
if (typeof raw.createdEventBlob !== "string")
|
|
37309
|
-
return null;
|
|
37310
|
-
return {
|
|
37311
|
-
createdEventBlob: raw.createdEventBlob,
|
|
37312
|
-
contractId: typeof raw.contractId === "string" ? raw.contractId : undefined,
|
|
37313
|
-
synchronizerId: typeof raw.synchronizerId === "string" ? raw.synchronizerId : undefined,
|
|
37314
|
-
};
|
|
37315
|
-
}
|
|
37316
|
-
/**
|
|
37317
|
-
* Build a Splice token-standard transfer of `amount` of `token` from `sender`
|
|
37318
|
-
* to `receiver`, carrying `memo`, ready to hand to a wallet's prepareExecute.
|
|
37319
|
-
* Returns the full prepareExecute params (commandId + commands + actAs +
|
|
37320
|
-
* disclosedContracts).
|
|
37321
|
-
*/
|
|
37322
|
-
async function buildCantonTransfer(connector, { sender, receiver, amount, token, memo, nowMs, }) {
|
|
37323
|
-
if (!token.originalAddress) {
|
|
37324
|
-
throw new Error("Canton token originalAddress is required");
|
|
37325
|
-
}
|
|
37326
|
-
const instrument = parseCantonInstrument(token.originalAddress);
|
|
37327
|
-
if (!instrument) {
|
|
37328
|
-
throw new Error(`Invalid Canton token address: ${token.originalAddress}`);
|
|
37329
|
-
}
|
|
37330
|
-
const registryUrl = resolveCantonRegistryUrl(instrument);
|
|
37331
|
-
const inputHoldingCids = await fetchInputHoldingCids(connector, sender, instrument, amount);
|
|
37332
|
-
const choiceArgs = buildTransferChoiceArgs({
|
|
37333
|
-
sender,
|
|
37334
|
-
receiver,
|
|
37335
|
-
amount,
|
|
37336
|
-
instrument,
|
|
37337
|
-
inputHoldingCids,
|
|
37338
|
-
memo,
|
|
37339
|
-
nowMs,
|
|
37340
|
-
});
|
|
37341
|
-
const factory = await fetchTransferFactory(registryUrl, choiceArgs);
|
|
37342
|
-
const choiceContextData = factory.choiceContext.choiceContextData;
|
|
37343
|
-
if (!choiceContextData) {
|
|
37344
|
-
throw new Error("Transfer factory choice context is missing");
|
|
37345
|
-
}
|
|
37346
|
-
choiceArgs.extraArgs.context = choiceContextData;
|
|
37347
|
-
const disclosedContracts = (factory.choiceContext?.disclosedContracts ?? []).flatMap((raw) => {
|
|
37348
|
-
const disclosedContract = toDisclosedContract(raw);
|
|
37349
|
-
return disclosedContract ? [disclosedContract] : [];
|
|
37350
|
-
});
|
|
37351
|
-
// Command ID: client-supplied identifier used to deduplicate submissions
|
|
37352
|
-
const commandId = `canton-${memo}`;
|
|
37353
|
-
return {
|
|
37354
|
-
commandId,
|
|
37355
|
-
commands: [
|
|
37356
|
-
{
|
|
37357
|
-
ExerciseCommand: {
|
|
37358
|
-
templateId: TRANSFER_FACTORY_INTERFACE_ID,
|
|
37359
|
-
contractId: factory.factoryId,
|
|
37360
|
-
choice: TRANSFER_FACTORY_CHOICE,
|
|
37361
|
-
choiceArgument: buildPrepareChoiceArgument(choiceArgs),
|
|
37362
|
-
},
|
|
37363
|
-
},
|
|
37364
|
-
],
|
|
37365
|
-
actAs: [sender],
|
|
37366
|
-
disclosedContracts,
|
|
37367
|
-
};
|
|
37368
|
-
}
|
|
37369
|
-
|
|
37370
37584
|
const useExecuteTransaction = (squidRoute) => {
|
|
37371
37585
|
const { fromChain, toChain, fromToken, toToken, isSameChain } = useSwap();
|
|
37372
37586
|
// A route completes on source tx only if it's same-chain AND
|
|
@@ -37981,7 +38195,7 @@ const useExecuteTransaction = (squidRoute) => {
|
|
|
37981
38195
|
}
|
|
37982
38196
|
const amount = formatBNToReadable(fromAmount, token.decimals);
|
|
37983
38197
|
dispatchSignatureRequestEvent(route);
|
|
37984
|
-
const
|
|
38198
|
+
const result = await cantonSigner.transfer({
|
|
37985
38199
|
sender: sourceUserAddress,
|
|
37986
38200
|
receiver,
|
|
37987
38201
|
amount,
|
|
@@ -37989,15 +38203,14 @@ const useExecuteTransaction = (squidRoute) => {
|
|
|
37989
38203
|
memo: orderHash,
|
|
37990
38204
|
nowMs: Date.now(),
|
|
37991
38205
|
});
|
|
37992
|
-
const
|
|
37993
|
-
const txHash = result.tx?.payload?.updateId ?? "";
|
|
38206
|
+
const txHash = result.updateId;
|
|
37994
38207
|
if (txHash) {
|
|
37995
38208
|
resetQueriesAfterTxSigned();
|
|
37996
38209
|
}
|
|
37997
38210
|
WidgetEvents.getInstance().dispatchSwapExecuteCall(route, txHash);
|
|
37998
38211
|
const txParams = setTransactionState({
|
|
37999
38212
|
route,
|
|
38000
|
-
txHash
|
|
38213
|
+
txHash,
|
|
38001
38214
|
userAddress: sourceUserAddress,
|
|
38002
38215
|
status: TransactionStatus.INITIAL_LOADING,
|
|
38003
38216
|
sourceStatus: TransactionStatus.ONGOING,
|
|
@@ -38280,7 +38493,7 @@ refetchIntervalInBackground = false, refetchInterval = 30000, quoteOnly = true,
|
|
|
38280
38493
|
//
|
|
38281
38494
|
// If deposit address is not selected, we use the connected address as the source address instead
|
|
38282
38495
|
const sourceUserAddress = isDepositAddressActive
|
|
38283
|
-
? depositRefundAddress ?? sourceConnectedAddress
|
|
38496
|
+
? (depositRefundAddress ?? sourceConnectedAddress)
|
|
38284
38497
|
: sourceConnectedAddress;
|
|
38285
38498
|
const squidRouteQueryKeys = useMemo(() => keys().transaction(fromChain?.chainId, toChain?.chainId, toToken?.address, fromToken?.address, fromPrice, config.slippage, sourceUserAddress, config.degenMode, destinationAddress, fallbackAddress, quoteOnly, fromChain?.chainType, config.preHook, config.postHook, config.overrideGasRefundAddress), [
|
|
38286
38499
|
fromChain?.chainId,
|
|
@@ -38572,6 +38785,10 @@ const useAutoConnect = () => {
|
|
|
38572
38785
|
const { chains } = useSquidChains();
|
|
38573
38786
|
const { isInitialized } = useConfigStore();
|
|
38574
38787
|
const hasAttemptedReconnect = useRef(false);
|
|
38788
|
+
const hasAttemptedCantonReconnect = useRef(false);
|
|
38789
|
+
const { autoConnectCanton, wallets: cantonWallets } = useCantonContext();
|
|
38790
|
+
const recentCantonWalletId = useWalletStore((state) => state.recentConnectorIds[ChainType.CANTON]);
|
|
38791
|
+
const hasRecentCantonWallet = cantonWallets.some((wallet) => wallet.connectorId === recentCantonWalletId);
|
|
38575
38792
|
const { autoConnectEvm } = useEvmContext();
|
|
38576
38793
|
const { autoConnectSolana } = useSolanaContext();
|
|
38577
38794
|
const { autoConnectSui } = useSuiContext();
|
|
@@ -38639,6 +38856,15 @@ const useAutoConnect = () => {
|
|
|
38639
38856
|
useEffect(() => {
|
|
38640
38857
|
autoConnect();
|
|
38641
38858
|
}, [autoConnect]);
|
|
38859
|
+
useEffect(() => {
|
|
38860
|
+
if (!isInitialized ||
|
|
38861
|
+
chains.length === 0 ||
|
|
38862
|
+
!hasRecentCantonWallet ||
|
|
38863
|
+
hasAttemptedCantonReconnect.current)
|
|
38864
|
+
return;
|
|
38865
|
+
hasAttemptedCantonReconnect.current = true;
|
|
38866
|
+
autoConnectCanton().catch(() => undefined);
|
|
38867
|
+
}, [autoConnectCanton, hasRecentCantonWallet, isInitialized, chains.length]);
|
|
38642
38868
|
return { autoConnect };
|
|
38643
38869
|
};
|
|
38644
38870
|
|
|
@@ -38947,5 +39173,5 @@ const SquidProvider = ({ children, config, placeholder, }) => {
|
|
|
38947
39173
|
React.createElement(CosmosProvider, null, children))))))))))) : (placeholder);
|
|
38948
39174
|
};
|
|
38949
39175
|
|
|
38950
|
-
export { useWalletStore as $, AxelarStatusResponseType as A, useHederaAccountActivation as B, CHAIN_IDS as C, DEFAULT_LOCALE as D, useHederaTokenAssociations as E, useKeyboardNavigation as F, useSquidQueryClient as G, HistoryTxType as H, useSquid as I, useStellarAccountActivation as J, useStellarTrustLine as K, useAddressBookStore as L, useAssetsColorsStore as M, Nr as N, useFavoriteTokensStore as O, useHistoryStore as P, QueryKeys as Q, useSendTransactionStore as R, SquidStatusErrorType as S, TransactionErrorType as T, useConfigStore as U, useSquidStore as V, Wo as W, XamanXrplNetwork as X, useSwapRoutePersistStore as Y, useTransactionStore as Z, ConnectingWalletStatus as _, WindowWalletFlag as a, useAvailableQuotes as a$, useDepositAddress as a0, useSwap as a1, buildUrlSearchParamsFromSwapEvent as a2, parseInitialAssetsFromUrl as a3, useUrlSwapParams as a4, useAllConnectedWalletBalances as a5, useAllTokensWithBalanceForChainType as a6, useCosmosBalance as a7, useEvmBalance as a8, useMultiChainBalance as a9, useRouteWarnings as aA, useSendTransactionStatus as aB, useSwapTransactionStatus as aC, useAvatar as aD, useHistory as aE, useDebouncedValue as aF, useAddToken as aG, useAutoConnect as aH, useEnsDataForAddress as aI, useEnsSearch as aJ, useGnosisContext as aK, useIsSameAddressAndGnosisContext as aL, useIntegratorContext as aM, useMultiChainWallet as aN, useSigner as aO, useWallet as aP, useWallets as aQ, useXrplTrustLine as aR, TX_STATUS_CONSTANTS as aS, FINAL_TRANSACTION_STATUSES as aT, useGetFiatQuote as aU, useGetOnRampConfig as aV, useExecuteFiatQuote as aW, useFiatOnRampTxStatus as aX, useFiatTransactions as aY, useCurrencyDetails as aZ, useCountryDetails as a_, useMultipleTokenPrices as aa, useBitcoinNativeBalance as ab, useCosmosNativeBalance as ac, useEvmNativeBalance as ad, useNativeBalance as ae, useSolanaNativeBalance as af, useStellarNativeBalance as ag, useSuiNativeBalance as ah, useXrplNativeBalance as ai, useNativeTokenForChain as aj, useSingleTokenPrice as ak, useSourceChainGasToken as al, useSquidTokens as am, useHistoricalData as an, useTokensData as ao, useEstimateSendTransaction as ap, useSendTransaction as aq, useSendTransactionGas as ar, useAllTransactionsStatus as as, useApproval as at, useDepositTransactionStatus as au, useEstimate as av, useEstimatePriceImpact as aw, useExecuteTransaction as ax, useGetRoute as ay, useGetRouteWrapper as az, DEFAULT_ROUTE_REFETCH_INTERVAL as b,
|
|
38951
|
-
//# sourceMappingURL=index-
|
|
39176
|
+
export { useWalletStore as $, AxelarStatusResponseType as A, useHederaAccountActivation as B, CHAIN_IDS as C, DEFAULT_LOCALE as D, useHederaTokenAssociations as E, useKeyboardNavigation as F, useSquidQueryClient as G, HistoryTxType as H, useSquid as I, useStellarAccountActivation as J, useStellarTrustLine as K, useAddressBookStore as L, useAssetsColorsStore as M, Nr as N, useFavoriteTokensStore as O, useHistoryStore as P, QueryKeys as Q, useSendTransactionStore as R, SquidStatusErrorType as S, TransactionErrorType as T, useConfigStore as U, useSquidStore as V, Wo as W, XamanXrplNetwork as X, useSwapRoutePersistStore as Y, useTransactionStore as Z, ConnectingWalletStatus as _, WindowWalletFlag as a, useAvailableQuotes as a$, useDepositAddress as a0, useSwap as a1, buildUrlSearchParamsFromSwapEvent as a2, parseInitialAssetsFromUrl as a3, useUrlSwapParams as a4, useAllConnectedWalletBalances as a5, useAllTokensWithBalanceForChainType as a6, useCosmosBalance as a7, useEvmBalance as a8, useMultiChainBalance as a9, useRouteWarnings as aA, useSendTransactionStatus as aB, useSwapTransactionStatus as aC, useAvatar as aD, useHistory as aE, useDebouncedValue as aF, useAddToken as aG, useAutoConnect as aH, useEnsDataForAddress as aI, useEnsSearch as aJ, useGnosisContext as aK, useIsSameAddressAndGnosisContext as aL, useIntegratorContext as aM, useMultiChainWallet as aN, useSigner as aO, useWallet as aP, useWallets as aQ, useXrplTrustLine as aR, TX_STATUS_CONSTANTS as aS, FINAL_TRANSACTION_STATUSES as aT, useGetFiatQuote as aU, useGetOnRampConfig as aV, useExecuteFiatQuote as aW, useFiatOnRampTxStatus as aX, useFiatTransactions as aY, useCurrencyDetails as aZ, useCountryDetails as a_, useMultipleTokenPrices as aa, useBitcoinNativeBalance as ab, useCosmosNativeBalance as ac, useEvmNativeBalance as ad, useNativeBalance as ae, useSolanaNativeBalance as af, useStellarNativeBalance as ag, useSuiNativeBalance as ah, useXrplNativeBalance as ai, useNativeTokenForChain as aj, useSingleTokenPrice as ak, useSourceChainGasToken as al, useSquidTokens as am, useHistoricalData as an, useTokensData as ao, useEstimateSendTransaction as ap, useSendTransaction as aq, useSendTransactionGas as ar, useAllTransactionsStatus as as, useApproval as at, useDepositTransactionStatus as au, useEstimate as av, useEstimatePriceImpact as aw, useExecuteTransaction as ax, useGetRoute as ay, useGetRouteWrapper as az, DEFAULT_ROUTE_REFETCH_INTERVAL as b, addEthereumChain as b$, useRecommendedQuote as b0, useGetOnrampPaymentTypes as b1, useSuggestedFiatAmounts as b2, SquidProvider as b3, EnsService as b4, getXummClient as b5, isXamanXAppContext as b6, getQueryHeaders as b7, getStatusCode as b8, is404Error as b9, randomIntFromInterval as bA, getTokensForChain as bB, getFirstAvailableChainId as bC, fetchHighestBalanceToken as bD, getInitialOrDefaultTokenAddressForChain as bE, getInitialTokenAddressForChain as bF, filterTokensForDestination as bG, getInitialChainIdFromConfig as bH, getCosmosKey as bI, getKeysSettled as bJ, getAllKeysForSupportedCosmosChains as bK, isCosmosAddressValid as bL, getCosmosSigningClient as bM, getCosmosChainInfosObject as bN, connectCosmosWallet as bO, isFallbackAddressNeeded as bP, suggestChainOrThrow as bQ, normalizeError as bR, transactionErrorCode as bS, isUserRejectionError as bT, getTransactionError as bU, handleTransactionErrorEvents as bV, isSwapRouteError as bW, isStatusError as bX, createQuoteRequestParamsHash as bY, WidgetEvents as bZ, EvmNetworkNotSupportedErrorCode as b_, assetsBaseUrl as ba, shareSubgraphId as bb, sortTokensBySharedSubgraphIds as bc, getSupportedChainIdsForDirection as bd, filterChains as be, filterTokens as bf, getTokenImage as bg, getNewSwapParamsFromInput as bh, sortAllTokens as bi, findToken as bj, findNativeToken as bk, normalizeIbcAddress as bl, groupTokensBySymbol as bm, groupTokensByChainId as bn, filterViewableTokens as bo, getSecretNetworkBalances as bp, getTokenAssetsKey as bq, fetchAssetsColors as br, isSupportedChainType as bs, initializeSquidData as bt, isEmptyObject as bu, normalizeTokenSymbol as bv, areTokenSymbolsCompatible as bw, isEvmosChain as bx, resolveChainIdFromAsset as by, getConfigWithDefaults as bz, chainTypeToNativeTokenAddressMap as c, chainflipMultihopBridgeType as c$, parseEvmAddress as c0, formatEvmWallet as c1, filterWagmiConnector as c2, waitForReceiptWithRetry as c3, getUserCountry as c4, getCountryData as c5, getCurrencyData as c6, adaptiveRound as c7, getSuggestedAmountsForCurrency as c8, HederaExtensionHelper as c9, isStellarIssuedToken as cA, getStellarHorizonApiUrl as cB, isValidIssuedAsset as cC, isValidHorizonAsset as cD, formatTransactionHistoryDate as cE, getAxelarExplorerTxUrl as cF, getSourceExplorerTxUrl as cG, getMainExplorerUrl as cH, formatDistance as cI, formatSeconds as cJ, formatSwapTxStatusResponseForStorage as cK, simplifyRouteAction as cL, fetchSwapTransactionStatus as cM, compareTransactionIds as cN, isCoralBridgeAction as cO, isActionCompletedOnSourceTx as cP, sleep as cQ, isChainflipDepositRoute as cR, isChainflipBridgeTransaction as cS, isOnChainTxData as cT, isDepositAddressDirectTransferRoute as cU, getHistoryTransactionId as cV, getStepStatuses as cW, getHalfSuccessState as cX, getStepsInfos as cY, getSwapTxStatusRefetchInterval as cZ, getSendTxStatusRefetchInterval as c_, convertHederaAccountIdToEvmAddress as ca, convertEvmAddressToHederaAccountId as cb, scaleHbarToWei as cc, scaleWeiToHbar as cd, parseToBigInt as ce, roundNumericValue as cf, formatUnitsRounded as cg, formatTokenAmount as ch, formatUsdAmount as ci, trimExtraDecimals as cj, getNumericValue as ck, cleanAmount as cl, convertTokenAmountToUSD as cm, convertUSDToTokenAmount as cn, calculateTotal24hChange as co, getRouteExpiry as cp, searchTokens as cq, filterSolanaWallets as cr, isSolanaAddressValid as cs, executeSolanaSwap as ct, executeSolanaTransfer as cu, isStellarAddressValid as cv, getStellarNetwork as cw, stellarAddressToScVal as cx, getStellarTrustLineAsset as cy, isStellarToken as cz, definedInWindow as d, getBridgeType as d0, getTransactionStatus as d1, getTransactionEndStatus as d2, isHistoryTransactionPending as d3, isHistoryTransactionFailed as d4, isHistoryTransactionWarning as d5, isHistoryTransactionEnded as d6, formatHash as d7, isWalletAddressValid as d8, redirectToExtensionsStore as d9, populateWallets as da, buildFallbackWallets as db, getDefaultChain as dc, sortWallets as dd, areSameAddress as de, sortAddressBook as df, calculateTotalUsdBalanceUSD as dg, addTokenToWallet as dh, isEvmChainNotSupportedError as di, getWalletSupportedChainTypes as dj, getConnectorForChainType as dk, walletSupportsChainType as dl, connectWallet as dm, cancelConnectWallet as dn, isProblematicConnector as dp, mergeWallets as dq, isXionSmartContractAddress as dr, isXrplAddressValid as ds, buildXrplTrustSetTx as dt, getXrplNetwork as du, parseXrplPaymentTx as dv, parseXrplTokenAddress as dw, er as e, formatBNToReadable as f, chainTypeToZeroAddressMap as g, destinationAddressResetValue as h, fallbackAddressResetValue as i, nativeCantonTokenAddress as j, nativeCosmosTokenAddress as k, nativeEvmTokenAddress as l, nativeSolanaTokenAddress as m, nativeBitcoinTokenAddress as n, nativeStellarTokenAddress as o, nativeSuiTokenAddress as p, nativeXrplTokenAddress as q, CosmosProvider as r, SendTransactionStatus as s, TransactionStatus as t, useCosmosContext as u, useTrackSearchEmpty as v, walletIconBaseUrl as w, useSquidChains as x, useClient as y, useCosmosForChain as z };
|
|
39177
|
+
//# sourceMappingURL=index-C3owE9lb.js.map
|