dev
dart 174 lines 5.71 KB
Raw
1 import 'dart:typed_data';
2
3 import 'package:cw_evm/usdt0/usdt0_quote.dart';
4 import 'package:hex/hex.dart' as hex;
5 import 'package:web3dart/web3dart.dart' as web3;
6
7 /// Minimal OFT ABI for USDT0 (per docs.usdt0.to).
8 /// quoteSend(SendParam, bool payInLzToken) returns MessagingFee.
9 /// send(SendParam, MessagingFee, address refundAddress) payable.
10 /// SendParam: dstEid, to (bytes32), amountLD (amount to send, token units),
11 /// minAmountLD (min receive on destination), extraOptions, composeMsg, oftCmd.
12 const String _oftAbiJson = '''
13 [
14 {
15 "inputs": [
16 {
17 "components": [
18 {"internalType": "uint32", "name": "dstEid", "type": "uint32"},
19 {"internalType": "bytes32", "name": "to", "type": "bytes32"},
20 {"internalType": "uint256", "name": "amountLD", "type": "uint256"},
21 {"internalType": "uint256", "name": "minAmountLD", "type": "uint256"},
22 {"internalType": "bytes", "name": "extraOptions", "type": "bytes"},
23 {"internalType": "bytes", "name": "composeMsg", "type": "bytes"},
24 {"internalType": "bytes", "name": "oftCmd", "type": "bytes"}
25 ],
26 "internalType": "struct SendParam",
27 "name": "sendParam",
28 "type": "tuple"
29 },
30 {"internalType": "bool", "name": "payInLzToken", "type": "bool"}
31 ],
32 "name": "quoteSend",
33 "outputs": [
34 {
35 "components": [
36 {"internalType": "uint256", "name": "nativeFee", "type": "uint256"},
37 {"internalType": "uint256", "name": "lzTokenFee", "type": "uint256"}
38 ],
39 "internalType": "struct MessagingFee",
40 "name": "",
41 "type": "tuple"
42 }
43 ],
44 "stateMutability": "view",
45 "type": "function"
46 },
47 {
48 "inputs": [
49 {
50 "components": [
51 {"internalType": "uint32", "name": "dstEid", "type": "uint32"},
52 {"internalType": "bytes32", "name": "to", "type": "bytes32"},
53 {"internalType": "uint256", "name": "amountLD", "type": "uint256"},
54 {"internalType": "uint256", "name": "minAmountLD", "type": "uint256"},
55 {"internalType": "bytes", "name": "extraOptions", "type": "bytes"},
56 {"internalType": "bytes", "name": "composeMsg", "type": "bytes"},
57 {"internalType": "bytes", "name": "oftCmd", "type": "bytes"}
58 ],
59 "internalType": "struct SendParam",
60 "name": "_sendParam",
61 "type": "tuple"
62 },
63 {
64 "components": [
65 {"internalType": "uint256", "name": "nativeFee", "type": "uint256"},
66 {"internalType": "uint256", "name": "lzTokenFee", "type": "uint256"}
67 ],
68 "internalType": "struct MessagingFee",
69 "name": "_fee",
70 "type": "tuple"
71 },
72 {"internalType": "address", "name": "_refundAddress", "type": "address"}
73 ],
74 "name": "send",
75 "outputs": [],
76 "stateMutability": "payable",
77 "type": "function"
78 }
79 ]
80 ''';
81
82 final web3.ContractAbi oftContractAbi = web3.ContractAbi.fromJson(_oftAbiJson, 'OFT');
83
84 /// OFT contract wrapper for quoteSend (view) and send (payable).
85 class OFT {
86 OFT({
87 required web3.EthereumAddress address,
88 required web3.Web3Client client,
89 }) : _address = address,
90 _client = client;
91
92 final web3.EthereumAddress _address;
93 final web3.Web3Client _client;
94
95 /// Calls quoteSend and returns MessagingFee (nativeFee, lzTokenFee).
96 Future<USDT0Quote> quoteSend({
97 required int dstEid,
98 required List<int> toBytes32,
99 required BigInt amountLD,
100 required BigInt minAmountLD,
101 Uint8List? extraOptions,
102 Uint8List? composeMsg,
103 Uint8List? oftCmd,
104 bool payInLzToken = false,
105 }) async {
106 final contract = web3.DeployedContract(
107 oftContractAbi,
108 _address,
109 );
110 final fn = contract.function('quoteSend');
111 final sendParam = [
112 BigInt.from(dstEid),
113 Uint8List.fromList(toBytes32),
114 amountLD,
115 minAmountLD,
116 extraOptions ?? Uint8List(0),
117 composeMsg ?? Uint8List(0),
118 oftCmd ?? Uint8List(0),
119 ];
120 final params = [sendParam, payInLzToken];
121
122 final result = await _client.call(
123 contract: contract,
124 function: fn,
125 params: params,
126 );
127
128 final msgFee = result.first as List<dynamic>;
129 final nativeFee = msgFee[0] as BigInt;
130 final lzTokenFee = msgFee[1] as BigInt;
131 return USDT0Quote(nativeFee: nativeFee, lzTokenFee: lzTokenFee);
132 }
133
134 /// Encodes send(SendParam, MessagingFee, refundAddress) for transaction.
135 /// Returns (dataHex, valueWei) where valueWei is the native fee to send.
136 ({String dataHex, BigInt valueWei}) encodeSend({
137 required int dstEid,
138 required List<int> toBytes32,
139 required BigInt amountLD,
140 required BigInt minAmountLD,
141 required BigInt nativeFee,
142 required BigInt lzTokenFee,
143 required String refundAddress,
144 Uint8List? extraOptions,
145 Uint8List? composeMsg,
146 Uint8List? oftCmd,
147 }) {
148 final contract = web3.DeployedContract(oftContractAbi, _address);
149 final fn = contract.function('send');
150 final sendParam = [
151 BigInt.from(dstEid),
152 Uint8List.fromList(toBytes32),
153 amountLD,
154 minAmountLD,
155 extraOptions ?? Uint8List(0),
156 composeMsg ?? Uint8List(0),
157 oftCmd ?? Uint8List(0),
158 ];
159 final fee = [nativeFee, lzTokenFee];
160 final refund = web3.EthereumAddress.fromHex(refundAddress);
161 final encoded = fn.encodeCall([sendParam, fee, refund]);
162 final dataHex = '0x${hex.HEX.encode(encoded)}';
163 return (dataHex: dataHex, valueWei: nativeFee);
164 }
165 }
166
167 /// Converts EVM address (0x + 40 hex) to bytes32 (left-padded).
168 List<int> addressToBytes32(String address) {
169 final clean = address.startsWith('0x') ? address.substring(2) : address;
170
171 if (clean.length != 40) throw ArgumentError('Invalid address length');
172
173 return hex.HEX.decode('000000000000000000000000$clean');
174 }