@ember-finance/sdk 2.21.0 → 2.23.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/src/abis/CcipRouter.json +58 -0
- package/dist/src/abis/index.d.ts +3 -1
- package/dist/src/abis/index.js +4 -1
- package/dist/src/api/v2/apis/accounts-api.d.ts +2 -0
- package/dist/src/api/v2/apis/accounts-api.js +3 -1
- package/dist/src/api/v2/models/account-transaction-transaction-data.d.ts +2 -1
- package/dist/src/api/v2/models/account-transaction.d.ts +2 -0
- package/dist/src/api/v2/models/account-transaction.js +3 -1
- package/dist/src/api/v2/models/bridge-transaction.d.ts +50 -0
- package/dist/src/api/v2/models/bridge-transaction.js +14 -0
- package/dist/src/api/v2/models/chain.d.ts +6 -0
- package/dist/src/api/v2/models/evm-ccip-lane.d.ts +73 -0
- package/dist/src/api/v2/models/evm-ccip-lane.js +14 -0
- package/dist/src/api/v2/models/evm-ccip-rate-limits.d.ts +36 -0
- package/dist/src/api/v2/models/evm-ccip-rate-limits.js +14 -0
- package/dist/src/api/v2/models/evm-ccip-token-pool.d.ts +122 -0
- package/dist/src/api/v2/models/evm-ccip-token-pool.js +17 -0
- package/dist/src/api/v2/models/evm-contracts.d.ts +9 -0
- package/dist/src/api/v2/models/evm-oftadapter.d.ts +11 -0
- package/dist/src/api/v2/models/evm-oftadapter.js +4 -1
- package/dist/src/api/v2/models/evm-oftpeer.d.ts +12 -0
- package/dist/src/api/v2/models/index.d.ts +4 -0
- package/dist/src/api/v2/models/index.js +4 -0
- package/dist/src/evm-vaults/interfaces/index.d.ts +39 -0
- package/dist/src/evm-vaults/on-chain-calls/tx-builder.d.ts +15 -1
- package/dist/src/evm-vaults/on-chain-calls/tx-builder.js +36 -0
- package/dist/src/evm-vaults/on-chain-calls/user.d.ts +40 -1
- package/dist/src/evm-vaults/on-chain-calls/user.js +75 -0
- package/dist/src/evm-vaults/on-chain-calls/vault-reader.d.ts +14 -1
- package/dist/src/evm-vaults/on-chain-calls/vault-reader.js +30 -0
- package/dist/src/evm-vaults/utils/ccip.d.ts +23 -0
- package/dist/src/evm-vaults/utils/ccip.js +32 -0
- package/dist/src/evm-vaults/utils/deployment-parser.d.ts +46 -1
- package/dist/src/evm-vaults/utils/deployment-parser.js +74 -0
- package/dist/src/evm-vaults/utils/index.d.ts +1 -0
- package/dist/src/evm-vaults/utils/index.js +1 -0
- package/dist/src/sui-vaults/on-chain-calls/operator.d.ts +10 -1
- package/dist/src/sui-vaults/on-chain-calls/operator.js +22 -2
- package/dist/src/sui-vaults/on-chain-calls/protocol-config-reader.d.ts +8 -0
- package/dist/src/sui-vaults/on-chain-calls/protocol-config-reader.js +10 -0
- package/dist/src/sui-vaults/on-chain-calls/tx-builder.d.ts +7 -0
- package/dist/src/sui-vaults/on-chain-calls/tx-builder.js +26 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EVMDeploymentParser } from "../utils/index.js";
|
|
2
|
-
import { EvmProtocolInfo, VaultPauseOperation, ITransactionCall, IPermitSignature, IOFTSendParam, IOFTMessagingFee } from "../interfaces/index.js";
|
|
2
|
+
import { EvmProtocolInfo, VaultPauseOperation, ITransactionCall, IPermitSignature, IOFTSendParam, IOFTMessagingFee, ICcipSendParam } from "../interfaces/index.js";
|
|
3
3
|
import { NumStr } from "../../common/types.js";
|
|
4
4
|
/**
|
|
5
5
|
* EVM Transaction Builder for Ember Protocol (ERC-4626 Compatible)
|
|
@@ -543,6 +543,20 @@ export declare class EVMTxBuilder {
|
|
|
543
543
|
* @returns Transaction call object
|
|
544
544
|
*/
|
|
545
545
|
bridgeSend(adapterAddress: string, sendParam: IOFTSendParam, fee: IOFTMessagingFee, refundAddress: string): ITransactionCall;
|
|
546
|
+
/**
|
|
547
|
+
* Builds the CCIP send call.
|
|
548
|
+
*
|
|
549
|
+
* The counterpart to bridgeSend, but the call goes to Chainlink's Router
|
|
550
|
+
* rather than to our own pool: the Router pulls the tokens, charges the fee
|
|
551
|
+
* and hands off to the pool. That is also why the approval for a CCIP bridge
|
|
552
|
+
* goes to the Router and not to the pool.
|
|
553
|
+
*
|
|
554
|
+
* @param routerAddress The CCIP Router on the source chain
|
|
555
|
+
* @param sendParam Destination, receiver and amount
|
|
556
|
+
* @param tokenAddress The receipt token being bridged
|
|
557
|
+
* @param fee Fee from getCcipFeeQuote, sent as value when paying natively
|
|
558
|
+
*/
|
|
559
|
+
ccipSend(routerAddress: string, sendParam: ICcipSendParam, tokenAddress: string, fee: bigint): ITransactionCall;
|
|
546
560
|
private atomicCall;
|
|
547
561
|
/** Claim the caller's own escrowed (deferred) holdback collateral. */
|
|
548
562
|
atomicClaimHoldback(vaultAddress: string): ITransactionCall;
|
|
@@ -4,6 +4,8 @@ import EmberETHVaultABI from "../../abis/EmberETHVault.json" with { type: "json"
|
|
|
4
4
|
import EmberProtocolConfigABI from "../../abis/EmberProtocolConfig.json" with { type: "json" };
|
|
5
5
|
import ERC20TokenABI from "../../abis/ERC20Token.json" with { type: "json" };
|
|
6
6
|
import EmberVaultOFTAdapterABI from "../../abis/EmberVaultMintBurnOFTAdapter.json" with { type: "json" };
|
|
7
|
+
import CcipRouterABI from "../../abis/CcipRouter.json" with { type: "json" };
|
|
8
|
+
import { ZERO_ADDRESS, encodeCcipReceiver, encodeCcipExtraArgs } from "../utils/ccip.js";
|
|
7
9
|
import AtomicLiquidityABI from "../../abis/AtomicLiquidity.json" with { type: "json" };
|
|
8
10
|
import EmberDepositParkingLotABI from "../../abis/EmberDepositParkingLot.json" with { type: "json" };
|
|
9
11
|
import OpenCoverVaultABI from "../../abis/OpenCoverVault.json" with { type: "json" };
|
|
@@ -1147,6 +1149,40 @@ export class EVMTxBuilder {
|
|
|
1147
1149
|
chainId: this.getChainId()
|
|
1148
1150
|
};
|
|
1149
1151
|
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Builds the CCIP send call.
|
|
1154
|
+
*
|
|
1155
|
+
* The counterpart to bridgeSend, but the call goes to Chainlink's Router
|
|
1156
|
+
* rather than to our own pool: the Router pulls the tokens, charges the fee
|
|
1157
|
+
* and hands off to the pool. That is also why the approval for a CCIP bridge
|
|
1158
|
+
* goes to the Router and not to the pool.
|
|
1159
|
+
*
|
|
1160
|
+
* @param routerAddress The CCIP Router on the source chain
|
|
1161
|
+
* @param sendParam Destination, receiver and amount
|
|
1162
|
+
* @param tokenAddress The receipt token being bridged
|
|
1163
|
+
* @param fee Fee from getCcipFeeQuote, sent as value when paying natively
|
|
1164
|
+
*/
|
|
1165
|
+
ccipSend(routerAddress, sendParam, tokenAddress, fee) {
|
|
1166
|
+
const feeToken = sendParam.feeToken ?? ZERO_ADDRESS;
|
|
1167
|
+
return {
|
|
1168
|
+
to: routerAddress,
|
|
1169
|
+
functionName: "ccipSend",
|
|
1170
|
+
args: [
|
|
1171
|
+
sendParam.destinationChainSelector,
|
|
1172
|
+
{
|
|
1173
|
+
receiver: encodeCcipReceiver(sendParam.receiver),
|
|
1174
|
+
data: "0x",
|
|
1175
|
+
tokenAmounts: [{ token: tokenAddress, amount: sendParam.amount }],
|
|
1176
|
+
feeToken,
|
|
1177
|
+
extraArgs: encodeCcipExtraArgs(sendParam.destinationGasLimit)
|
|
1178
|
+
}
|
|
1179
|
+
],
|
|
1180
|
+
abi: CcipRouterABI.abi,
|
|
1181
|
+
// Paying in a token means the Router pulls it instead, so no value.
|
|
1182
|
+
value: feeToken === ZERO_ADDRESS ? fee : 0n,
|
|
1183
|
+
chainId: this.getChainId()
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1150
1186
|
// ============================================
|
|
1151
1187
|
// Atomic Liquidity Vault methods (BFE-817/818)
|
|
1152
1188
|
//
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Signer, Provider } from "ethers";
|
|
2
2
|
import { EVMOnChainCalls } from "./onchain-calls.js";
|
|
3
|
-
import { EvmProtocolInfo, IUserCallOptions, IEvmCallOptions, ICoverVaultCallOptions, EvmOnChainCallResponse, IPermitSignature, IETHDepositOptions, IBridgeSendOptions, IOFTFeeQuote } from "../interfaces/index.js";
|
|
3
|
+
import { EvmProtocolInfo, IUserCallOptions, IEvmCallOptions, ICoverVaultCallOptions, EvmOnChainCallResponse, IPermitSignature, IETHDepositOptions, IBridgeSendOptions, IOFTFeeQuote, ICcipFeeQuote, ICcipBridgeSendOptions } from "../interfaces/index.js";
|
|
4
4
|
import { NumStr } from "../../common/types.js";
|
|
5
5
|
import { Chain } from "../../api/v2/index.js";
|
|
6
6
|
/**
|
|
@@ -423,6 +423,45 @@ export declare class EVMUserCalls extends EVMOnChainCalls {
|
|
|
423
423
|
* @returns Transaction execution response
|
|
424
424
|
*/
|
|
425
425
|
bridgeSendToken(destinationChain: Chain, tokenAddress: string, amount: NumStr, receiver: string, feeQuote: IOFTFeeQuote, options?: IBridgeSendOptions): Promise<EvmOnChainCallResponse>;
|
|
426
|
+
/**
|
|
427
|
+
* Bridges a receipt token to a destination chain using the vault's CCIP pool.
|
|
428
|
+
*
|
|
429
|
+
* The counterpart to bridgeSendToken. Call getBridgeTransportForVault first
|
|
430
|
+
* and pick the matching method — a vault has exactly one active bridge, and
|
|
431
|
+
* calling the wrong one throws rather than silently doing nothing.
|
|
432
|
+
*
|
|
433
|
+
* One difference from the LayerZero path worth knowing: the allowance goes
|
|
434
|
+
* to the CCIP Router, not to the pool, because the Router is what pulls the
|
|
435
|
+
* tokens. An unconfigured route throws here rather than reverting on chain.
|
|
436
|
+
*
|
|
437
|
+
* @param destinationChain Destination chain metadata (must include ccipChainSelector)
|
|
438
|
+
* @param tokenAddress Receipt token address (also the vault identifier in EVM deployment data)
|
|
439
|
+
* @param amount Amount to bridge in local token decimals
|
|
440
|
+
* @param receiver Recipient wallet address on the destination chain
|
|
441
|
+
* @param feeQuote Fee quote returned by reader.getCcipFeeQuote
|
|
442
|
+
* @param options Optional bridge/send overrides
|
|
443
|
+
* @returns Transaction execution response
|
|
444
|
+
*/
|
|
445
|
+
bridgeSendTokenViaCcip(destinationChain: Chain, tokenAddress: string, amount: NumStr, receiver: string, feeQuote: ICcipFeeQuote, options?: ICcipBridgeSendOptions): Promise<EvmOnChainCallResponse>;
|
|
446
|
+
/**
|
|
447
|
+
* Reports which bridge a vault uses, or undefined if it has none.
|
|
448
|
+
*
|
|
449
|
+
* A vault has exactly one active bridge, but different vaults use different
|
|
450
|
+
* ones, so branch on this rather than assuming either transport.
|
|
451
|
+
*
|
|
452
|
+
* @param tokenAddress Receipt token address (also the vault identifier in EVM deployment data)
|
|
453
|
+
*/
|
|
454
|
+
getBridgeTransportForVault(tokenAddress: string): "LayerZero" | "CCIP" | undefined;
|
|
455
|
+
/**
|
|
456
|
+
* Lists the networks a vault can bridge to, whichever transport it uses.
|
|
457
|
+
*
|
|
458
|
+
* Empty for a vault with no bridge, and for a bridged vault with no route
|
|
459
|
+
* configured yet. A network absent from this list cannot be sent to — the
|
|
460
|
+
* send would revert on chain — so drive the destination picker from it.
|
|
461
|
+
*
|
|
462
|
+
* @param tokenAddress Receipt token address (also the vault identifier in EVM deployment data)
|
|
463
|
+
*/
|
|
464
|
+
getBridgeDestinationsForVault(tokenAddress: string): string[];
|
|
426
465
|
/**
|
|
427
466
|
* Gets the vault contract instance for read operations
|
|
428
467
|
* @param vaultAddress The address of the vault
|
|
@@ -637,6 +637,81 @@ export class EVMUserCalls extends EVMOnChainCalls {
|
|
|
637
637
|
}, refundAddress);
|
|
638
638
|
return this.execCall(txCall, options);
|
|
639
639
|
}
|
|
640
|
+
/**
|
|
641
|
+
* Bridges a receipt token to a destination chain using the vault's CCIP pool.
|
|
642
|
+
*
|
|
643
|
+
* The counterpart to bridgeSendToken. Call getBridgeTransportForVault first
|
|
644
|
+
* and pick the matching method — a vault has exactly one active bridge, and
|
|
645
|
+
* calling the wrong one throws rather than silently doing nothing.
|
|
646
|
+
*
|
|
647
|
+
* One difference from the LayerZero path worth knowing: the allowance goes
|
|
648
|
+
* to the CCIP Router, not to the pool, because the Router is what pulls the
|
|
649
|
+
* tokens. An unconfigured route throws here rather than reverting on chain.
|
|
650
|
+
*
|
|
651
|
+
* @param destinationChain Destination chain metadata (must include ccipChainSelector)
|
|
652
|
+
* @param tokenAddress Receipt token address (also the vault identifier in EVM deployment data)
|
|
653
|
+
* @param amount Amount to bridge in local token decimals
|
|
654
|
+
* @param receiver Recipient wallet address on the destination chain
|
|
655
|
+
* @param feeQuote Fee quote returned by reader.getCcipFeeQuote
|
|
656
|
+
* @param options Optional bridge/send overrides
|
|
657
|
+
* @returns Transaction execution response
|
|
658
|
+
*/
|
|
659
|
+
async bridgeSendTokenViaCcip(destinationChain, tokenAddress, amount, receiver, feeQuote, options) {
|
|
660
|
+
if (!this.provider) {
|
|
661
|
+
throw new Error("Provider required for bridge send read operations and allowance checks");
|
|
662
|
+
}
|
|
663
|
+
const pool = this.parser.getCcipTokenPoolByVaultAddress(tokenAddress);
|
|
664
|
+
if (!pool?.router) {
|
|
665
|
+
throw new Error(`CCIP token pool not found for token address: ${tokenAddress}`);
|
|
666
|
+
}
|
|
667
|
+
if (destinationChain.ccipChainSelector === undefined ||
|
|
668
|
+
destinationChain.ccipChainSelector === null) {
|
|
669
|
+
throw new Error(`Destination chain "${destinationChain.name ?? destinationChain.identifier}" has no ccipChainSelector — cannot address a CCIP destination`);
|
|
670
|
+
}
|
|
671
|
+
const remoteChainSelector = destinationChain.ccipChainSelector;
|
|
672
|
+
const lane = this.parser.getCcipLaneByVaultAndSelector(tokenAddress, remoteChainSelector);
|
|
673
|
+
if (!lane) {
|
|
674
|
+
throw new Error(`No CCIP lane configured from this chain to "${destinationChain.identifier}" for token ${tokenAddress}`);
|
|
675
|
+
}
|
|
676
|
+
const amountBigInt = BigInt(amount);
|
|
677
|
+
// The Router moves the tokens, so it is the Router that needs approval.
|
|
678
|
+
const token = new ethers.Contract(tokenAddress, ["function allowance(address,address) view returns (uint256)"], this.provider);
|
|
679
|
+
const currentAllowance = await token.allowance(await this.getWalletAddress(), pool.router);
|
|
680
|
+
if (currentAllowance < amountBigInt) {
|
|
681
|
+
throw new Error(`Insufficient allowance for bridge transfer. Current allowance: ${currentAllowance}, required: ${amountBigInt}`);
|
|
682
|
+
}
|
|
683
|
+
const txCall = this.txBuilder.ccipSend(pool.router, {
|
|
684
|
+
destinationChainSelector: remoteChainSelector,
|
|
685
|
+
receiver,
|
|
686
|
+
amount: amountBigInt,
|
|
687
|
+
feeToken: options?.feeToken,
|
|
688
|
+
destinationGasLimit: options?.destinationGasLimit
|
|
689
|
+
}, tokenAddress, feeQuote.fee);
|
|
690
|
+
return this.execCall(txCall, options);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Reports which bridge a vault uses, or undefined if it has none.
|
|
694
|
+
*
|
|
695
|
+
* A vault has exactly one active bridge, but different vaults use different
|
|
696
|
+
* ones, so branch on this rather than assuming either transport.
|
|
697
|
+
*
|
|
698
|
+
* @param tokenAddress Receipt token address (also the vault identifier in EVM deployment data)
|
|
699
|
+
*/
|
|
700
|
+
getBridgeTransportForVault(tokenAddress) {
|
|
701
|
+
return this.parser.getBridgeTransportByVaultAddress(tokenAddress);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Lists the networks a vault can bridge to, whichever transport it uses.
|
|
705
|
+
*
|
|
706
|
+
* Empty for a vault with no bridge, and for a bridged vault with no route
|
|
707
|
+
* configured yet. A network absent from this list cannot be sent to — the
|
|
708
|
+
* send would revert on chain — so drive the destination picker from it.
|
|
709
|
+
*
|
|
710
|
+
* @param tokenAddress Receipt token address (also the vault identifier in EVM deployment data)
|
|
711
|
+
*/
|
|
712
|
+
getBridgeDestinationsForVault(tokenAddress) {
|
|
713
|
+
return this.parser.getBridgeDestinationsByVaultAddress(tokenAddress);
|
|
714
|
+
}
|
|
640
715
|
// ============================================
|
|
641
716
|
// Read-Only Methods
|
|
642
717
|
// ============================================
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Provider } from "ethers";
|
|
2
2
|
import { EVMDeploymentParser } from "../utils/deployment-parser.js";
|
|
3
|
-
import { EvmProtocolInfo, IOFTSendParam, IOFTFeeQuote, IOFTVaultSupportedChain, IParkingLotInfo } from "../interfaces/index.js";
|
|
3
|
+
import { EvmProtocolInfo, IOFTSendParam, IOFTFeeQuote, IOFTVaultSupportedChain, IParkingLotInfo, ICcipSendParam, ICcipFeeQuote } from "../interfaces/index.js";
|
|
4
4
|
import { NumStr } from "../../common/types.js";
|
|
5
5
|
import { Chain, VaultsApi } from "../../api/v2/index.js";
|
|
6
6
|
/**
|
|
@@ -524,4 +524,17 @@ export declare class EVMVaultReader {
|
|
|
524
524
|
* @returns Combined fee quote including native fee and expected amounts
|
|
525
525
|
*/
|
|
526
526
|
getOFTFeeQuote(vaultId: string, sendParam: IOFTSendParam, payInLzToken?: boolean): Promise<IOFTFeeQuote>;
|
|
527
|
+
/**
|
|
528
|
+
* Quotes what a CCIP bridge send will cost.
|
|
529
|
+
*
|
|
530
|
+
* The counterpart to getOFTFeeQuote. CCIP charges one fee rather than
|
|
531
|
+
* LayerZero's native/LZ-token pair, and quotes no received amount: a
|
|
532
|
+
* burn-and-mint pool mints exactly what it burned, so the amount received is
|
|
533
|
+
* the amount sent.
|
|
534
|
+
*
|
|
535
|
+
* @param vaultId The vault (receipt token) address on the source chain
|
|
536
|
+
* @param sendParam Destination, receiver and amount
|
|
537
|
+
* @returns The fee and the token it is charged in
|
|
538
|
+
*/
|
|
539
|
+
getCcipFeeQuote(vaultId: string, sendParam: ICcipSendParam): Promise<ICcipFeeQuote>;
|
|
527
540
|
}
|
|
@@ -4,6 +4,8 @@ import EmberVaultABI from "../../abis/EmberVault.json" with { type: "json" };
|
|
|
4
4
|
import EmberETHVaultABI from "../../abis/EmberETHVault.json" with { type: "json" };
|
|
5
5
|
import EmberProtocolConfigABI from "../../abis/EmberProtocolConfig.json" with { type: "json" };
|
|
6
6
|
import ERC20TokenABI from "../../abis/ERC20Token.json" with { type: "json" };
|
|
7
|
+
import CcipRouterABI from "../../abis/CcipRouter.json" with { type: "json" };
|
|
8
|
+
import { ZERO_ADDRESS, encodeCcipReceiver, encodeCcipExtraArgs } from "../utils/ccip.js";
|
|
7
9
|
import EmberVaultOFTAdapterABI from "../../abis/EmberVaultMintBurnOFTAdapter.json" with { type: "json" };
|
|
8
10
|
import EmberDepositParkingLotABI from "../../abis/EmberDepositParkingLot.json" with { type: "json" };
|
|
9
11
|
import { Configuration, VaultsApi } from "../../api/v2/index.js";
|
|
@@ -736,4 +738,32 @@ export class EVMVaultReader {
|
|
|
736
738
|
maxAmountLD: oftQuote.oftLimit.maxAmountLD
|
|
737
739
|
};
|
|
738
740
|
}
|
|
741
|
+
/**
|
|
742
|
+
* Quotes what a CCIP bridge send will cost.
|
|
743
|
+
*
|
|
744
|
+
* The counterpart to getOFTFeeQuote. CCIP charges one fee rather than
|
|
745
|
+
* LayerZero's native/LZ-token pair, and quotes no received amount: a
|
|
746
|
+
* burn-and-mint pool mints exactly what it burned, so the amount received is
|
|
747
|
+
* the amount sent.
|
|
748
|
+
*
|
|
749
|
+
* @param vaultId The vault (receipt token) address on the source chain
|
|
750
|
+
* @param sendParam Destination, receiver and amount
|
|
751
|
+
* @returns The fee and the token it is charged in
|
|
752
|
+
*/
|
|
753
|
+
async getCcipFeeQuote(vaultId, sendParam) {
|
|
754
|
+
const pool = this.parser.getCcipTokenPoolByVaultAddress(vaultId);
|
|
755
|
+
if (!pool?.router) {
|
|
756
|
+
throw new Error(`CCIP token pool not found for vault: ${vaultId}`);
|
|
757
|
+
}
|
|
758
|
+
const feeToken = sendParam.feeToken ?? ZERO_ADDRESS;
|
|
759
|
+
const router = new Contract(pool.router, CcipRouterABI.abi, this.provider);
|
|
760
|
+
const fee = await router.getFee(sendParam.destinationChainSelector, {
|
|
761
|
+
receiver: encodeCcipReceiver(sendParam.receiver),
|
|
762
|
+
data: "0x",
|
|
763
|
+
tokenAmounts: [{ token: vaultId, amount: sendParam.amount }],
|
|
764
|
+
feeToken,
|
|
765
|
+
extraArgs: encodeCcipExtraArgs(sendParam.destinationGasLimit)
|
|
766
|
+
});
|
|
767
|
+
return { fee, feeToken };
|
|
768
|
+
}
|
|
739
769
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The native currency, as CCIP names it when no fee token is given. */
|
|
2
|
+
export declare const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
3
|
+
/**
|
|
4
|
+
* Encodes a receiver for a CCIP message.
|
|
5
|
+
*
|
|
6
|
+
* CCIP carries the receiver as `bytes` rather than `address`, because the far
|
|
7
|
+
* end of a lane need not be an EVM chain. On an EVM destination that is the
|
|
8
|
+
* address left-padded to 32 bytes, which is why the same account looks
|
|
9
|
+
* different on the two sides of a transfer.
|
|
10
|
+
*/
|
|
11
|
+
export declare const encodeCcipReceiver: (receiver: string) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Encodes CCIP extraArgs.
|
|
14
|
+
*
|
|
15
|
+
* `allowOutOfOrderExecution` is true because a token transfer carries no
|
|
16
|
+
* message the destination has to apply in sequence; requiring ordering would
|
|
17
|
+
* make one stuck message block every later one on the lane.
|
|
18
|
+
*
|
|
19
|
+
* gasLimit is 0 by default: a plain token transfer runs no code on the
|
|
20
|
+
* destination beyond the pool's own mint, and paying for gas that is never used
|
|
21
|
+
* only raises the fee.
|
|
22
|
+
*/
|
|
23
|
+
export declare const encodeCcipExtraArgs: (destinationGasLimit?: bigint) => string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AbiCoder, getAddress, zeroPadValue } from "ethers";
|
|
2
|
+
/** The native currency, as CCIP names it when no fee token is given. */
|
|
3
|
+
export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
4
|
+
/**
|
|
5
|
+
* Tag for CCIP's EVMExtraArgsV2, the first four bytes of
|
|
6
|
+
* keccak256("CCIP EVMExtraArgsV2"). Verified against a deployed ramp.
|
|
7
|
+
*/
|
|
8
|
+
const EVM_EXTRA_ARGS_V2_TAG = "0x181dcf10";
|
|
9
|
+
/**
|
|
10
|
+
* Encodes a receiver for a CCIP message.
|
|
11
|
+
*
|
|
12
|
+
* CCIP carries the receiver as `bytes` rather than `address`, because the far
|
|
13
|
+
* end of a lane need not be an EVM chain. On an EVM destination that is the
|
|
14
|
+
* address left-padded to 32 bytes, which is why the same account looks
|
|
15
|
+
* different on the two sides of a transfer.
|
|
16
|
+
*/
|
|
17
|
+
export const encodeCcipReceiver = (receiver) => zeroPadValue(getAddress(receiver), 32);
|
|
18
|
+
/**
|
|
19
|
+
* Encodes CCIP extraArgs.
|
|
20
|
+
*
|
|
21
|
+
* `allowOutOfOrderExecution` is true because a token transfer carries no
|
|
22
|
+
* message the destination has to apply in sequence; requiring ordering would
|
|
23
|
+
* make one stuck message block every later one on the lane.
|
|
24
|
+
*
|
|
25
|
+
* gasLimit is 0 by default: a plain token transfer runs no code on the
|
|
26
|
+
* destination beyond the pool's own mint, and paying for gas that is never used
|
|
27
|
+
* only raises the fee.
|
|
28
|
+
*/
|
|
29
|
+
export const encodeCcipExtraArgs = (destinationGasLimit = 0n) => {
|
|
30
|
+
const encoded = AbiCoder.defaultAbiCoder().encode(["uint256", "bool"], [destinationGasLimit, true]);
|
|
31
|
+
return EVM_EXTRA_ARGS_V2_TAG + encoded.slice(2);
|
|
32
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EvmOFTAdapter } from "../../api/v2/index.js";
|
|
1
|
+
import { EvmCcipLane, EvmCcipTokenPool, EvmOFTAdapter } from "../../api/v2/index.js";
|
|
2
2
|
import { EvmProtocolInfo } from "../interfaces/index.js";
|
|
3
3
|
export declare class EVMDeploymentParser {
|
|
4
4
|
deployment: EvmProtocolInfo;
|
|
@@ -124,4 +124,49 @@ export declare class EVMDeploymentParser {
|
|
|
124
124
|
* @param vaultAddress The vault address (receipt token address in EVM deployment data)
|
|
125
125
|
*/
|
|
126
126
|
getOFTAdapterObjectByVaultAddress(vaultAddress: string): EvmOFTAdapter | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* Finds the CCIP token pool entry for a given vault address.
|
|
129
|
+
* @param vaultAddress The vault address (receipt token address in EVM deployment data)
|
|
130
|
+
*/
|
|
131
|
+
getCcipTokenPoolByVaultAddress(vaultAddress: string): EvmCcipTokenPool | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Reports which bridge a vault uses, or undefined if it has none.
|
|
134
|
+
*
|
|
135
|
+
* A vault has exactly one active bridge, but different vaults use different
|
|
136
|
+
* ones, so call this before choosing between the LayerZero and CCIP send
|
|
137
|
+
* methods rather than assuming either.
|
|
138
|
+
*
|
|
139
|
+
* @param vaultAddress The vault address (receipt token address in EVM deployment data)
|
|
140
|
+
*/
|
|
141
|
+
getBridgeTransportByVaultAddress(vaultAddress: string): "LayerZero" | "CCIP" | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* Lists the networks a vault can bridge to, whichever transport it uses.
|
|
144
|
+
*
|
|
145
|
+
* Returns an empty array for a vault with no bridge, so it is safe to render
|
|
146
|
+
* directly. Names are as the deployment records them, which is what
|
|
147
|
+
* bridgeSendTokenViaCcip takes as its destination.
|
|
148
|
+
*
|
|
149
|
+
* @param vaultAddress The vault address (receipt token address in EVM deployment data)
|
|
150
|
+
*/
|
|
151
|
+
getBridgeDestinationsByVaultAddress(vaultAddress: string): string[];
|
|
152
|
+
/**
|
|
153
|
+
* Finds the configured CCIP lane from a vault to a destination chain selector.
|
|
154
|
+
*
|
|
155
|
+
* A missing lane means the route is not configured and a send would revert.
|
|
156
|
+
*
|
|
157
|
+
* @param vaultAddress The vault address on the source chain
|
|
158
|
+
* @param remoteChainSelector The destination CCIP chain selector
|
|
159
|
+
*/
|
|
160
|
+
getCcipLaneByVaultAndSelector(vaultAddress: string, remoteChainSelector: string): EvmCcipLane | undefined;
|
|
161
|
+
/**
|
|
162
|
+
* Finds the configured CCIP lane from a vault to a destination network.
|
|
163
|
+
*
|
|
164
|
+
* Lanes are keyed by the destination's CCIP chain selector, which nothing
|
|
165
|
+
* outside CCIP knows, so this looks the lane up by network name instead.
|
|
166
|
+
* A missing lane means the route is not configured — sending would revert.
|
|
167
|
+
*
|
|
168
|
+
* @param vaultAddress The vault address on the source chain
|
|
169
|
+
* @param destinationNetwork The destination network name, e.g. "atlantic"
|
|
170
|
+
*/
|
|
171
|
+
getCcipLaneByVaultAndNetwork(vaultAddress: string, destinationNetwork: string): EvmCcipLane | undefined;
|
|
127
172
|
}
|
|
@@ -206,4 +206,78 @@ export class EVMDeploymentParser {
|
|
|
206
206
|
}
|
|
207
207
|
return entry;
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Finds the CCIP token pool entry for a given vault address.
|
|
211
|
+
* @param vaultAddress The vault address (receipt token address in EVM deployment data)
|
|
212
|
+
*/
|
|
213
|
+
getCcipTokenPoolByVaultAddress(vaultAddress) {
|
|
214
|
+
return Object.values(this.deployment.contracts?.ccipTokenPools ?? {}).find(pool => pool.vaultAddress?.toLowerCase() === vaultAddress.toLowerCase());
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Reports which bridge a vault uses, or undefined if it has none.
|
|
218
|
+
*
|
|
219
|
+
* A vault has exactly one active bridge, but different vaults use different
|
|
220
|
+
* ones, so call this before choosing between the LayerZero and CCIP send
|
|
221
|
+
* methods rather than assuming either.
|
|
222
|
+
*
|
|
223
|
+
* @param vaultAddress The vault address (receipt token address in EVM deployment data)
|
|
224
|
+
*/
|
|
225
|
+
getBridgeTransportByVaultAddress(vaultAddress) {
|
|
226
|
+
if (this.getCcipTokenPoolByVaultAddress(vaultAddress))
|
|
227
|
+
return "CCIP";
|
|
228
|
+
if (this.getOFTAdapterObjectByVaultAddress(vaultAddress))
|
|
229
|
+
return "LayerZero";
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Lists the networks a vault can bridge to, whichever transport it uses.
|
|
234
|
+
*
|
|
235
|
+
* Returns an empty array for a vault with no bridge, so it is safe to render
|
|
236
|
+
* directly. Names are as the deployment records them, which is what
|
|
237
|
+
* bridgeSendTokenViaCcip takes as its destination.
|
|
238
|
+
*
|
|
239
|
+
* @param vaultAddress The vault address (receipt token address in EVM deployment data)
|
|
240
|
+
*/
|
|
241
|
+
getBridgeDestinationsByVaultAddress(vaultAddress) {
|
|
242
|
+
// Both transports record the far end the same way, under remoteNetwork,
|
|
243
|
+
// but one keys its map by CCIP chain selector and the other by LayerZero
|
|
244
|
+
// endpoint id — neither of which a caller has any way to resolve. Reading
|
|
245
|
+
// the values rather than the keys is what keeps the two symmetric.
|
|
246
|
+
const pool = this.getCcipTokenPoolByVaultAddress(vaultAddress);
|
|
247
|
+
const adapter = this.getOFTAdapterObjectByVaultAddress(vaultAddress);
|
|
248
|
+
const networks = [
|
|
249
|
+
...Object.values(pool?.lanes ?? {}),
|
|
250
|
+
...Object.values(adapter?.peers ?? {})
|
|
251
|
+
]
|
|
252
|
+
.map(entry => entry.remoteNetwork)
|
|
253
|
+
.filter((network) => !!network);
|
|
254
|
+
return [...new Set(networks)];
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Finds the configured CCIP lane from a vault to a destination chain selector.
|
|
258
|
+
*
|
|
259
|
+
* A missing lane means the route is not configured and a send would revert.
|
|
260
|
+
*
|
|
261
|
+
* @param vaultAddress The vault address on the source chain
|
|
262
|
+
* @param remoteChainSelector The destination CCIP chain selector
|
|
263
|
+
*/
|
|
264
|
+
getCcipLaneByVaultAndSelector(vaultAddress, remoteChainSelector) {
|
|
265
|
+
// Lanes are keyed by selector, so this is a direct hit rather than the
|
|
266
|
+
// scan the by-network lookup has to do.
|
|
267
|
+
return this.getCcipTokenPoolByVaultAddress(vaultAddress)?.lanes?.[remoteChainSelector];
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Finds the configured CCIP lane from a vault to a destination network.
|
|
271
|
+
*
|
|
272
|
+
* Lanes are keyed by the destination's CCIP chain selector, which nothing
|
|
273
|
+
* outside CCIP knows, so this looks the lane up by network name instead.
|
|
274
|
+
* A missing lane means the route is not configured — sending would revert.
|
|
275
|
+
*
|
|
276
|
+
* @param vaultAddress The vault address on the source chain
|
|
277
|
+
* @param destinationNetwork The destination network name, e.g. "atlantic"
|
|
278
|
+
*/
|
|
279
|
+
getCcipLaneByVaultAndNetwork(vaultAddress, destinationNetwork) {
|
|
280
|
+
const pool = this.getCcipTokenPoolByVaultAddress(vaultAddress);
|
|
281
|
+
return Object.values(pool?.lanes ?? {}).find(lane => lane.remoteNetwork?.toLowerCase() === destinationNetwork.toLowerCase());
|
|
282
|
+
}
|
|
209
283
|
}
|
|
@@ -6,12 +6,21 @@ import { NumStr } from "@firefly-exchange/library-sui";
|
|
|
6
6
|
export declare class OperatorCalls extends OnChainCalls {
|
|
7
7
|
constructor(_network: string, _suiClient: SuiClient, _deployment: ISuiDeployment, _signer?: Signer, _walletAddress?: string);
|
|
8
8
|
/**
|
|
9
|
-
* Collect platform fee from a vault
|
|
9
|
+
* Collect platform fee from a vault, through whichever entry the protocol's
|
|
10
|
+
* current version accepts: `collect_platform_fee_v2` from VERSION 6, the
|
|
11
|
+
* legacy `collect_platform_fee` before it.
|
|
10
12
|
* @param vaultId The id of the vault to collect platform fee from
|
|
11
13
|
* @param options Optional tx execution params
|
|
12
14
|
* @returns OnChainCallResponse
|
|
13
15
|
*/
|
|
14
16
|
collectPlatformFee(vaultId: string, options?: IVaultOptionalParams & SuiTransactionBlockResponseOptions): Promise<OnChainCallResponse>;
|
|
17
|
+
/**
|
|
18
|
+
* Collect platform fee from a vault via `collect_platform_fee_v2` (VERSION 6+)
|
|
19
|
+
* @param vaultId The id of the vault to collect platform fee from
|
|
20
|
+
* @param options Optional tx execution params
|
|
21
|
+
* @returns OnChainCallResponse
|
|
22
|
+
*/
|
|
23
|
+
collectPlatformFeeV2(vaultId: string, options?: IVaultOptionalParams & SuiTransactionBlockResponseOptions): Promise<OnChainCallResponse>;
|
|
15
24
|
/**
|
|
16
25
|
* Charge platform fee from a vault
|
|
17
26
|
* @param vaultId The id of the vault to charge platform fee from
|
|
@@ -1,19 +1,39 @@
|
|
|
1
1
|
import { OnChainCalls } from "./onchain-calls.js";
|
|
2
2
|
import { TransactionBlock } from "@firefly-exchange/library-sui";
|
|
3
3
|
import { CoinUtils } from "@firefly-exchange/library-sui";
|
|
4
|
+
import { SuiProtocolConfigReader, usesCollectPlatformFeeV2 } from "./protocol-config-reader.js";
|
|
4
5
|
export class OperatorCalls extends OnChainCalls {
|
|
5
6
|
constructor(_network, _suiClient, _deployment, _signer, _walletAddress) {
|
|
6
7
|
super(_network, _suiClient, _deployment, _signer, _walletAddress);
|
|
7
8
|
}
|
|
8
9
|
/**
|
|
9
|
-
* Collect platform fee from a vault
|
|
10
|
+
* Collect platform fee from a vault, through whichever entry the protocol's
|
|
11
|
+
* current version accepts: `collect_platform_fee_v2` from VERSION 6, the
|
|
12
|
+
* legacy `collect_platform_fee` before it.
|
|
10
13
|
* @param vaultId The id of the vault to collect platform fee from
|
|
11
14
|
* @param options Optional tx execution params
|
|
12
15
|
* @returns OnChainCallResponse
|
|
13
16
|
*/
|
|
14
17
|
async collectPlatformFee(vaultId, options) {
|
|
18
|
+
const { version } = await new SuiProtocolConfigReader(this.suiClient, this.parser.getProtocolConfig()).getProtocolConfigState();
|
|
15
19
|
const txb = options?.txBlock || new TransactionBlock();
|
|
16
|
-
|
|
20
|
+
if (usesCollectPlatformFeeV2(version)) {
|
|
21
|
+
this.txBuilder.collectPlatformFeeV2(vaultId, { ...options, txBlock: txb });
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this.txBuilder.collectPlatformFee(vaultId, { ...options, txBlock: txb });
|
|
25
|
+
}
|
|
26
|
+
return this.execCall(txb, options);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Collect platform fee from a vault via `collect_platform_fee_v2` (VERSION 6+)
|
|
30
|
+
* @param vaultId The id of the vault to collect platform fee from
|
|
31
|
+
* @param options Optional tx execution params
|
|
32
|
+
* @returns OnChainCallResponse
|
|
33
|
+
*/
|
|
34
|
+
async collectPlatformFeeV2(vaultId, options) {
|
|
35
|
+
const txb = options?.txBlock || new TransactionBlock();
|
|
36
|
+
this.txBuilder.collectPlatformFeeV2(vaultId, { ...options, txBlock: txb });
|
|
17
37
|
return this.execCall(txb, options);
|
|
18
38
|
}
|
|
19
39
|
/**
|
|
@@ -2,6 +2,14 @@ import { BaseClient as SuiClient } from "@mysten/sui/client";
|
|
|
2
2
|
import { IProtocolConfigState } from "../interfaces/index.js";
|
|
3
3
|
/** The protocol's sentinel for "no guardian". */
|
|
4
4
|
export declare const SUI_ZERO_ADDRESS: string;
|
|
5
|
+
/** First ProtocolConfig.version whose package aborts `collect_platform_fee`. */
|
|
6
|
+
export declare const SUI_COLLECT_PLATFORM_FEE_V2_VERSION = 6;
|
|
7
|
+
/**
|
|
8
|
+
* Whether platform fees must be collected through `collect_platform_fee_v2`.
|
|
9
|
+
* @param protocolVersion ProtocolConfig.version
|
|
10
|
+
* @returns True from VERSION 6 on
|
|
11
|
+
*/
|
|
12
|
+
export declare function usesCollectPlatformFeeV2(protocolVersion: string | number): boolean;
|
|
5
13
|
export declare class SuiProtocolConfigReader {
|
|
6
14
|
suiClient: SuiClient;
|
|
7
15
|
protocolConfigId: string;
|
|
@@ -4,6 +4,16 @@ import { normalizeSuiAddress } from "@mysten/sui/utils";
|
|
|
4
4
|
const GUARDIAN_FIELD_KEY = "guardian";
|
|
5
5
|
/** The protocol's sentinel for "no guardian". */
|
|
6
6
|
export const SUI_ZERO_ADDRESS = normalizeSuiAddress("0x0");
|
|
7
|
+
/** First ProtocolConfig.version whose package aborts `collect_platform_fee`. */
|
|
8
|
+
export const SUI_COLLECT_PLATFORM_FEE_V2_VERSION = 6;
|
|
9
|
+
/**
|
|
10
|
+
* Whether platform fees must be collected through `collect_platform_fee_v2`.
|
|
11
|
+
* @param protocolVersion ProtocolConfig.version
|
|
12
|
+
* @returns True from VERSION 6 on
|
|
13
|
+
*/
|
|
14
|
+
export function usesCollectPlatformFeeV2(protocolVersion) {
|
|
15
|
+
return Number(protocolVersion) >= SUI_COLLECT_PLATFORM_FEE_V2_VERSION;
|
|
16
|
+
}
|
|
7
17
|
/** gRPC flattens Move structs; JSON-RPC keeps a `fields` wrapper. */
|
|
8
18
|
const unwrapMoveFields = (value) => value?.fields ?? value;
|
|
9
19
|
export class SuiProtocolConfigReader {
|
|
@@ -353,6 +353,13 @@ export declare class TxBuilder {
|
|
|
353
353
|
* @returns TransactionBlock
|
|
354
354
|
*/
|
|
355
355
|
collectPlatformFee(vaultId: string, options?: ITxBuilderOptionalParams): TransactionBlock;
|
|
356
|
+
/**
|
|
357
|
+
* Collect platform fee (package VERSION 6+, where `collect_platform_fee` aborts)
|
|
358
|
+
* @param vaultId Id of the vault
|
|
359
|
+
* @param options Optional tx building params
|
|
360
|
+
* @returns TransactionBlock
|
|
361
|
+
*/
|
|
362
|
+
collectPlatformFeeV2(vaultId: string, options?: ITxBuilderOptionalParams): TransactionBlock;
|
|
356
363
|
/**
|
|
357
364
|
* Deposit asset
|
|
358
365
|
* @param vaultId Id of the vault
|
|
@@ -1041,6 +1041,32 @@ export class TxBuilder {
|
|
|
1041
1041
|
txb.setSenderIfNotSet(options.sender);
|
|
1042
1042
|
return txb;
|
|
1043
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Collect platform fee (package VERSION 6+, where `collect_platform_fee` aborts)
|
|
1046
|
+
* @param vaultId Id of the vault
|
|
1047
|
+
* @param options Optional tx building params
|
|
1048
|
+
* @returns TransactionBlock
|
|
1049
|
+
*/
|
|
1050
|
+
collectPlatformFeeV2(vaultId, options) {
|
|
1051
|
+
const txb = options?.txBlock || new TransactionBlock();
|
|
1052
|
+
txb.moveCall({
|
|
1053
|
+
arguments: [
|
|
1054
|
+
txb.object(vaultId),
|
|
1055
|
+
txb.object(this.parser.getProtocolConfig()),
|
|
1056
|
+
txb.object(SUI_CLOCK_OBJECT_ID)
|
|
1057
|
+
],
|
|
1058
|
+
typeArguments: [
|
|
1059
|
+
this.parser.getDepositCoinType(vaultId),
|
|
1060
|
+
this.parser.getReceiptCoinType(vaultId)
|
|
1061
|
+
],
|
|
1062
|
+
target: `${this.targetPackageId(options)}::gateway::collect_platform_fee_v2`
|
|
1063
|
+
});
|
|
1064
|
+
if (options?.gasBudget)
|
|
1065
|
+
txb.setGasBudget(options.gasBudget);
|
|
1066
|
+
if (options?.sender)
|
|
1067
|
+
txb.setSenderIfNotSet(options.sender);
|
|
1068
|
+
return txb;
|
|
1069
|
+
}
|
|
1044
1070
|
/**
|
|
1045
1071
|
* Deposit asset
|
|
1046
1072
|
* @param vaultId Id of the vault
|