@aboutcircles/sdk-transfers 0.1.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/TransferBuilder.d.ts +79 -0
- package/dist/TransferBuilder.d.ts.map +1 -0
- package/dist/TransferBuilder.js +333 -0
- package/dist/TransferBuilder.test.d.ts +2 -0
- package/dist/TransferBuilder.test.d.ts.map +1 -0
- package/dist/TransferBuilder.test.js +402 -0
- package/dist/errors.d.ts +42 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +83 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8966 -0
- package/package.json +41 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Address, AdvancedTransferOptions } from '@aboutcircles/sdk-types';
|
|
2
|
+
import type { Core } from '@aboutcircles/sdk-core';
|
|
3
|
+
/**
|
|
4
|
+
* TransferBuilder constructs transfer transactions without executing them
|
|
5
|
+
* Handles pathfinding, wrapped token unwrapping/wrapping, and flow matrix construction
|
|
6
|
+
*/
|
|
7
|
+
export declare class TransferBuilder {
|
|
8
|
+
private core;
|
|
9
|
+
private rpc;
|
|
10
|
+
constructor(core: Core);
|
|
11
|
+
/**
|
|
12
|
+
* Construct an advanced transfer transaction
|
|
13
|
+
* Returns the list of transactions to execute without executing them
|
|
14
|
+
*
|
|
15
|
+
* @param from Sender address
|
|
16
|
+
* @param to Recipient address
|
|
17
|
+
* @param amount Amount to transfer (in atto-circles)
|
|
18
|
+
* @param options Advanced transfer options
|
|
19
|
+
* @returns Array of transactions to execute in order
|
|
20
|
+
*/
|
|
21
|
+
constructAdvancedTransfer(from: Address, to: Address, amount: number | bigint, options?: AdvancedTransferOptions): Promise<Array<{
|
|
22
|
+
to: Address;
|
|
23
|
+
data: `0x${string}`;
|
|
24
|
+
value: bigint;
|
|
25
|
+
}>>;
|
|
26
|
+
/**
|
|
27
|
+
* Construct a replenish transaction to convert wrapped/other tokens into unwrapped personal CRC
|
|
28
|
+
* This uses pathfinder to find the best way to convert available tokens (including wrapped tokens)
|
|
29
|
+
* into the sender's own unwrapped ERC1155 personal CRC tokens
|
|
30
|
+
*
|
|
31
|
+
* @param avatarAddress The avatar address to replenish (convert tokens to their personal CRC)
|
|
32
|
+
* @param options Optional pathfinding options
|
|
33
|
+
* @returns Array of transactions to execute in order to perform the replenish
|
|
34
|
+
*/
|
|
35
|
+
constructReplenish(avatarAddress: Address, options?: Omit<AdvancedTransferOptions, 'txData'>): Promise<Array<{
|
|
36
|
+
to: Address;
|
|
37
|
+
data: `0x${string}`;
|
|
38
|
+
value: bigint;
|
|
39
|
+
}>>;
|
|
40
|
+
/**
|
|
41
|
+
* Fetches token balances and creates a map for quick lookup
|
|
42
|
+
*
|
|
43
|
+
* @param from Source avatar address
|
|
44
|
+
* @returns Map of token address to balance (in static units)
|
|
45
|
+
*/
|
|
46
|
+
private _getTokenBalanceMap;
|
|
47
|
+
/**
|
|
48
|
+
* Creates unwrap transaction calls for demurraged ERC20 wrapped tokens
|
|
49
|
+
* Unwraps only the exact amount used in the path
|
|
50
|
+
*
|
|
51
|
+
* @param wrappedTokensInPath Map of wrapped token addresses to [amount used in path, type]
|
|
52
|
+
* @returns Array of unwrap transaction calls for demurraged tokens
|
|
53
|
+
*/
|
|
54
|
+
private _createDemurragedUnwrapCalls;
|
|
55
|
+
/**
|
|
56
|
+
* Creates unwrap and wrap transaction calls for inflationary ERC20 wrapped tokens
|
|
57
|
+
* Unwraps the entire balance, then wraps back leftover tokens after transfer
|
|
58
|
+
*
|
|
59
|
+
* @param wrappedTokensInPath Map of wrapped token addresses to [amount used in path, type]
|
|
60
|
+
* @param tokenInfoMap Map of token addresses to TokenInfo
|
|
61
|
+
* @param balanceMap Map of token address to balance
|
|
62
|
+
* @returns Object containing unwrap and wrap transaction calls for inflationary tokens
|
|
63
|
+
*/
|
|
64
|
+
private _createInflationaryUnwrapAndWrapCalls;
|
|
65
|
+
/**
|
|
66
|
+
* Helper method to truncate amount to 6 decimals
|
|
67
|
+
*/
|
|
68
|
+
private _truncateToSixDecimals;
|
|
69
|
+
/**
|
|
70
|
+
* Get default token exclusion list for transfers to group mint handlers
|
|
71
|
+
* If the recipient is a group mint handler, exclude the group token and its wrappers
|
|
72
|
+
*
|
|
73
|
+
* @param to Recipient address
|
|
74
|
+
* @param excludeFromTokens Existing token exclusion list
|
|
75
|
+
* @returns Complete token exclusion list, or undefined if empty
|
|
76
|
+
*/
|
|
77
|
+
private _getDefaultTokenExcludeList;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=TransferBuilder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransferBuilder.d.ts","sourceRoot":"","sources":["../src/TransferBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,uBAAuB,EAAqB,MAAM,yBAAyB,CAAC;AACnG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAYnD;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,GAAG,CAAa;gBAEZ,IAAI,EAAE,IAAI;IAKtB;;;;;;;;;OASG;IACG,yBAAyB,CAC7B,IAAI,EAAE,OAAO,EACb,EAAE,EAAE,OAAO,EACX,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAgLtE;;;;;;;;OAQG;IACG,kBAAkB,CACtB,aAAa,EAAE,OAAO,EACtB,OAAO,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,QAAQ,CAAC,GAChD,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAetE;;;;;OAKG;YACW,mBAAmB;IAUjC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IAkCpC;;;;;;;;OAQG;IACH,OAAO,CAAC,qCAAqC;IAmE7C;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAM9B;;;;;;;OAOG;YACW,2BAA2B;CAoC1C"}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { createFlowMatrix as createFlowMatrixUtil, getTokenInfoMapFromPath, getWrappedTokensFromPath, replaceWrappedTokensWithAvatars, } from '@aboutcircles/sdk-pathfinder';
|
|
2
|
+
import { CirclesRpc } from '@aboutcircles/sdk-rpc';
|
|
3
|
+
import { bytesToHex, CirclesConverter, encodeFunctionData, ZERO_ADDRESS } from '@aboutcircles/sdk-utils';
|
|
4
|
+
import { InflationaryCirclesContract, DemurrageCirclesContract, CirclesType } from '@aboutcircles/sdk-core';
|
|
5
|
+
import { TransferError } from './errors';
|
|
6
|
+
/**
|
|
7
|
+
* TransferBuilder constructs transfer transactions without executing them
|
|
8
|
+
* Handles pathfinding, wrapped token unwrapping/wrapping, and flow matrix construction
|
|
9
|
+
*/
|
|
10
|
+
export class TransferBuilder {
|
|
11
|
+
core;
|
|
12
|
+
rpc;
|
|
13
|
+
constructor(core) {
|
|
14
|
+
this.core = core;
|
|
15
|
+
this.rpc = new CirclesRpc(core.config.circlesRpcUrl);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Construct an advanced transfer transaction
|
|
19
|
+
* Returns the list of transactions to execute without executing them
|
|
20
|
+
*
|
|
21
|
+
* @param from Sender address
|
|
22
|
+
* @param to Recipient address
|
|
23
|
+
* @param amount Amount to transfer (in atto-circles)
|
|
24
|
+
* @param options Advanced transfer options
|
|
25
|
+
* @returns Array of transactions to execute in order
|
|
26
|
+
*/
|
|
27
|
+
async constructAdvancedTransfer(from, to, amount, options) {
|
|
28
|
+
// Normalize addresses
|
|
29
|
+
const fromAddr = from.toLowerCase();
|
|
30
|
+
const toAddr = to.toLowerCase();
|
|
31
|
+
const amountBigInt = BigInt(amount);
|
|
32
|
+
// @todo move logic to separate function
|
|
33
|
+
// Optimization: Check if this is a self-transfer unwrap operation
|
|
34
|
+
// If sender == recipient and we have exactly one fromToken and one toToken,
|
|
35
|
+
// we can check if it's an unwrap operation and skip pathfinding
|
|
36
|
+
if (fromAddr === toAddr &&
|
|
37
|
+
options?.fromTokens?.length === 1 &&
|
|
38
|
+
options?.toTokens?.length === 1) {
|
|
39
|
+
const fromTokenAddr = options.fromTokens[0];
|
|
40
|
+
const toTokenAddr = options.toTokens[0];
|
|
41
|
+
// Use lift contract to check if fromToken is a wrapper and determine its type
|
|
42
|
+
const [demurragedWrapper, inflationaryWrapper] = await Promise.all([
|
|
43
|
+
this.core.liftERC20.erc20Circles(CirclesType.Demurrage, toTokenAddr),
|
|
44
|
+
this.core.liftERC20.erc20Circles(CirclesType.Inflation, toTokenAddr)
|
|
45
|
+
]);
|
|
46
|
+
// Check if fromToken is a demurraged wrapper for the toToken avatar
|
|
47
|
+
if (fromTokenAddr.toLowerCase() === demurragedWrapper.toLowerCase() &&
|
|
48
|
+
demurragedWrapper !== ZERO_ADDRESS) {
|
|
49
|
+
// Use demurraged wrapper contract to unwrap
|
|
50
|
+
const wrapper = new DemurrageCirclesContract({
|
|
51
|
+
address: fromTokenAddr,
|
|
52
|
+
rpcUrl: this.core.config.circlesRpcUrl
|
|
53
|
+
});
|
|
54
|
+
const unwrapTx = wrapper.unwrap(amountBigInt);
|
|
55
|
+
return [{
|
|
56
|
+
to: unwrapTx.to,
|
|
57
|
+
data: unwrapTx.data,
|
|
58
|
+
value: unwrapTx.value ?? 0n
|
|
59
|
+
}];
|
|
60
|
+
}
|
|
61
|
+
// Check if fromToken is an inflationary wrapper for the toToken avatar
|
|
62
|
+
if (fromTokenAddr.toLowerCase() === inflationaryWrapper.toLowerCase() &&
|
|
63
|
+
inflationaryWrapper !== ZERO_ADDRESS) {
|
|
64
|
+
// Use inflationary wrapper contract to unwrap
|
|
65
|
+
const wrapper = new InflationaryCirclesContract({
|
|
66
|
+
address: fromTokenAddr,
|
|
67
|
+
rpcUrl: this.core.config.circlesRpcUrl
|
|
68
|
+
});
|
|
69
|
+
// Convert demurraged amount to static atto circles for inflationary unwrap
|
|
70
|
+
const unwrapAmount = CirclesConverter.attoCirclesToAttoStaticCircles(amountBigInt);
|
|
71
|
+
const unwrapTx = wrapper.unwrap(unwrapAmount);
|
|
72
|
+
return [{
|
|
73
|
+
to: unwrapTx.to,
|
|
74
|
+
data: unwrapTx.data,
|
|
75
|
+
value: unwrapTx.value ?? 0n
|
|
76
|
+
}];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Truncate to 6 decimals for precision
|
|
80
|
+
const truncatedAmount = this._truncateToSixDecimals(amountBigInt);
|
|
81
|
+
// Get default token exclude list if sending to a group mint handler
|
|
82
|
+
const completeExcludeFromTokens = await this._getDefaultTokenExcludeList(toAddr, options?.excludeFromTokens);
|
|
83
|
+
// Update options with complete exclude list
|
|
84
|
+
const pathfindingOptions = {
|
|
85
|
+
...options,
|
|
86
|
+
...(completeExcludeFromTokens ? { excludeFromTokens: completeExcludeFromTokens } : {}),
|
|
87
|
+
};
|
|
88
|
+
let path = await this.rpc.pathfinder.findPath({
|
|
89
|
+
from: fromAddr,
|
|
90
|
+
to: toAddr,
|
|
91
|
+
targetFlow: truncatedAmount,
|
|
92
|
+
...pathfindingOptions,
|
|
93
|
+
});
|
|
94
|
+
// Check if path is valid
|
|
95
|
+
if (!path.transfers || path.transfers.length === 0) {
|
|
96
|
+
throw TransferError.noPathFound(fromAddr, toAddr);
|
|
97
|
+
}
|
|
98
|
+
// Check if pathfinder found enough tokens for the requested amount
|
|
99
|
+
if (path.maxFlow < truncatedAmount) {
|
|
100
|
+
throw TransferError.insufficientBalance(truncatedAmount, path.maxFlow, fromAddr, toAddr);
|
|
101
|
+
}
|
|
102
|
+
// Get token info for all tokens in the path using pathfinder utility
|
|
103
|
+
// @dev returning a Map<string, TokenInfo>
|
|
104
|
+
const tokenInfoMap = await getTokenInfoMapFromPath(fromAddr, this.core.config.circlesRpcUrl, path);
|
|
105
|
+
// Get wrapped tokens found in the path with their amounts and types
|
|
106
|
+
// @dev returning a Record<string (wrapperAddress), [bigint (amount used in path), string (type)]>
|
|
107
|
+
const wrappedTokensInPath = getWrappedTokensFromPath(path, tokenInfoMap);
|
|
108
|
+
// @todo maybe there is an easier way to check if there are wrapped tokens
|
|
109
|
+
const hasWrappedTokens = Object.keys(wrappedTokensInPath).length > 0;
|
|
110
|
+
// Validate that wrapped tokens are enabled if they're needed
|
|
111
|
+
if (hasWrappedTokens && !options?.useWrappedBalances) {
|
|
112
|
+
throw TransferError.wrappedTokensRequired();
|
|
113
|
+
}
|
|
114
|
+
let unwrapCalls = [];
|
|
115
|
+
let wrapCalls = [];
|
|
116
|
+
if (hasWrappedTokens) {
|
|
117
|
+
// Fetch token balances once for both unwrap and wrap operations
|
|
118
|
+
const balanceMap = await this._getTokenBalanceMap(fromAddr);
|
|
119
|
+
// Create unwrap calls for demurraged tokens (unwrap exact amount used in path)
|
|
120
|
+
const demurragedUnwrapCalls = this._createDemurragedUnwrapCalls(wrappedTokensInPath);
|
|
121
|
+
// Create unwrap and wrap calls for inflationary tokens
|
|
122
|
+
// Unwrap entire balance, then wrap back leftovers after transfer
|
|
123
|
+
const { unwrapCalls: inflationaryUnwrapCalls, wrapCalls: inflationaryWrapCalls } = this._createInflationaryUnwrapAndWrapCalls(wrappedTokensInPath, tokenInfoMap, balanceMap);
|
|
124
|
+
// Combine all unwrap calls
|
|
125
|
+
unwrapCalls = [...demurragedUnwrapCalls, ...inflationaryUnwrapCalls];
|
|
126
|
+
wrapCalls = inflationaryWrapCalls;
|
|
127
|
+
// Replace wrapped token addresses with avatar addresses in the path
|
|
128
|
+
path = replaceWrappedTokensWithAvatars(path, tokenInfoMap);
|
|
129
|
+
}
|
|
130
|
+
// Create flow matrix from the (possibly rewritten) path
|
|
131
|
+
const flowMatrix = createFlowMatrixUtil(fromAddr, toAddr, path.maxFlow, path.transfers);
|
|
132
|
+
// If txData is provided, attach it to the streams
|
|
133
|
+
if (options?.txData && flowMatrix.streams.length > 0) {
|
|
134
|
+
flowMatrix.streams[0].data = options.txData;
|
|
135
|
+
}
|
|
136
|
+
// Convert Uint8Array data to hex strings for ABI encoding
|
|
137
|
+
const streamsWithHexData = flowMatrix.streams.map((stream) => ({
|
|
138
|
+
sourceCoordinate: stream.sourceCoordinate,
|
|
139
|
+
flowEdgeIds: stream.flowEdgeIds,
|
|
140
|
+
data: stream.data instanceof Uint8Array ? bytesToHex(stream.data) : stream.data,
|
|
141
|
+
}));
|
|
142
|
+
// Create the operateFlowMatrix transaction
|
|
143
|
+
const operateFlowMatrixTx = this.core.hubV2.operateFlowMatrix(flowMatrix.flowVertices, flowMatrix.flowEdges, streamsWithHexData, flowMatrix.packedCoordinates);
|
|
144
|
+
// Check if self-approval is needed
|
|
145
|
+
// If the check fails (e.g., network error), we'll include the approval anyway to be safe
|
|
146
|
+
let isApproved = false;
|
|
147
|
+
try {
|
|
148
|
+
isApproved = await this.core.hubV2.isApprovedForAll(fromAddr, fromAddr);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
// If checking approval fails, assume not approved and include the approval transaction
|
|
152
|
+
console.warn('Failed to check approval status, including approval transaction:', error);
|
|
153
|
+
}
|
|
154
|
+
// Assemble all transactions in strict order:
|
|
155
|
+
// 1. Self-approval (only if not already approved)
|
|
156
|
+
// 2. All unwraps
|
|
157
|
+
// 3. operateFlowMatrix
|
|
158
|
+
// 4. All wraps (for leftover inflationary tokens)
|
|
159
|
+
const allTransactions = [
|
|
160
|
+
...(isApproved ? [] : [this.core.hubV2.setApprovalForAll(fromAddr, true)]),
|
|
161
|
+
...unwrapCalls,
|
|
162
|
+
operateFlowMatrixTx,
|
|
163
|
+
...wrapCalls,
|
|
164
|
+
];
|
|
165
|
+
return allTransactions;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Construct a replenish transaction to convert wrapped/other tokens into unwrapped personal CRC
|
|
169
|
+
* This uses pathfinder to find the best way to convert available tokens (including wrapped tokens)
|
|
170
|
+
* into the sender's own unwrapped ERC1155 personal CRC tokens
|
|
171
|
+
*
|
|
172
|
+
* @param avatarAddress The avatar address to replenish (convert tokens to their personal CRC)
|
|
173
|
+
* @param options Optional pathfinding options
|
|
174
|
+
* @returns Array of transactions to execute in order to perform the replenish
|
|
175
|
+
*/
|
|
176
|
+
async constructReplenish(avatarAddress, options) {
|
|
177
|
+
// @todo Implement replenish functionality
|
|
178
|
+
// This should:
|
|
179
|
+
// 1. Find maximum flow from avatar to itself targeting personal tokens
|
|
180
|
+
// 2. Handle wrapped token unwrapping similar to constructAdvancedTransfer
|
|
181
|
+
// 3. Create flow matrix for self-transfer
|
|
182
|
+
// 4. Handle wrap calls for leftover inflationary tokens
|
|
183
|
+
throw new Error('constructReplenish is not yet implemented. Please use constructAdvancedTransfer with same from/to address as a workaround.');
|
|
184
|
+
}
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Private Helper Methods
|
|
187
|
+
// ============================================================================
|
|
188
|
+
/**
|
|
189
|
+
* Fetches token balances and creates a map for quick lookup
|
|
190
|
+
*
|
|
191
|
+
* @param from Source avatar address
|
|
192
|
+
* @returns Map of token address to balance (in static units)
|
|
193
|
+
*/
|
|
194
|
+
async _getTokenBalanceMap(from) {
|
|
195
|
+
const allBalances = await this.rpc.balance.getTokenBalances(from);
|
|
196
|
+
const balanceMap = new Map();
|
|
197
|
+
// @todo remove any
|
|
198
|
+
allBalances.forEach((balance) => {
|
|
199
|
+
balanceMap.set(balance.tokenAddress.toLowerCase(), balance.staticAttoCircles);
|
|
200
|
+
});
|
|
201
|
+
return balanceMap;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Creates unwrap transaction calls for demurraged ERC20 wrapped tokens
|
|
205
|
+
* Unwraps only the exact amount used in the path
|
|
206
|
+
*
|
|
207
|
+
* @param wrappedTokensInPath Map of wrapped token addresses to [amount used in path, type]
|
|
208
|
+
* @returns Array of unwrap transaction calls for demurraged tokens
|
|
209
|
+
*/
|
|
210
|
+
_createDemurragedUnwrapCalls(wrappedTokensInPath) {
|
|
211
|
+
const unwrapCalls = [];
|
|
212
|
+
for (const [wrapperAddr, [amountUsedInPath, type]] of Object.entries(wrappedTokensInPath)) {
|
|
213
|
+
// Only process demurraged wrappers
|
|
214
|
+
if (type !== 'CrcV2_ERC20WrapperDeployed_Demurraged') {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
// Create unwrap call for the exact amount used in path
|
|
218
|
+
const data = encodeFunctionData({
|
|
219
|
+
abi: [{
|
|
220
|
+
type: 'function',
|
|
221
|
+
name: 'unwrap',
|
|
222
|
+
inputs: [{ name: '_amount', type: 'uint256' }],
|
|
223
|
+
outputs: [],
|
|
224
|
+
stateMutability: 'nonpayable',
|
|
225
|
+
}],
|
|
226
|
+
functionName: 'unwrap',
|
|
227
|
+
args: [amountUsedInPath],
|
|
228
|
+
});
|
|
229
|
+
unwrapCalls.push({
|
|
230
|
+
to: wrapperAddr,
|
|
231
|
+
data,
|
|
232
|
+
value: 0n,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return unwrapCalls;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Creates unwrap and wrap transaction calls for inflationary ERC20 wrapped tokens
|
|
239
|
+
* Unwraps the entire balance, then wraps back leftover tokens after transfer
|
|
240
|
+
*
|
|
241
|
+
* @param wrappedTokensInPath Map of wrapped token addresses to [amount used in path, type]
|
|
242
|
+
* @param tokenInfoMap Map of token addresses to TokenInfo
|
|
243
|
+
* @param balanceMap Map of token address to balance
|
|
244
|
+
* @returns Object containing unwrap and wrap transaction calls for inflationary tokens
|
|
245
|
+
*/
|
|
246
|
+
_createInflationaryUnwrapAndWrapCalls(wrappedTokensInPath, tokenInfoMap, balanceMap) {
|
|
247
|
+
const unwrapCalls = [];
|
|
248
|
+
const wrapCalls = [];
|
|
249
|
+
for (const [wrapperAddr, [amountUsedInPath, type]] of Object.entries(wrappedTokensInPath)) {
|
|
250
|
+
// Only process inflationary wrappers
|
|
251
|
+
if (type !== 'CrcV2_ERC20WrapperDeployed_Inflationary') {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const tokenInfo = tokenInfoMap.get(wrapperAddr.toLowerCase());
|
|
255
|
+
const currentBalance = balanceMap.get(wrapperAddr.toLowerCase()) || 0n;
|
|
256
|
+
if (currentBalance === 0n) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
// Create unwrap call for the entire balance (in static units)
|
|
260
|
+
const unwrapData = encodeFunctionData({
|
|
261
|
+
abi: [{
|
|
262
|
+
type: 'function',
|
|
263
|
+
name: 'unwrap',
|
|
264
|
+
inputs: [{ name: '_amount', type: 'uint256' }],
|
|
265
|
+
outputs: [],
|
|
266
|
+
stateMutability: 'nonpayable',
|
|
267
|
+
}],
|
|
268
|
+
functionName: 'unwrap',
|
|
269
|
+
args: [currentBalance],
|
|
270
|
+
});
|
|
271
|
+
unwrapCalls.push({
|
|
272
|
+
to: wrapperAddr,
|
|
273
|
+
data: unwrapData,
|
|
274
|
+
value: 0n,
|
|
275
|
+
});
|
|
276
|
+
// Calculate leftover amount: balance before unwrap (converted to demurraged) - amount used in path
|
|
277
|
+
const tokenOwner = tokenInfo?.tokenOwner;
|
|
278
|
+
const leftoverAmount = CirclesConverter.attoStaticCirclesToAttoCircles(currentBalance) - amountUsedInPath;
|
|
279
|
+
// Only create wrap call if there's leftover amount
|
|
280
|
+
if (leftoverAmount > 0n) {
|
|
281
|
+
// Create wrap call using hubV2 contract
|
|
282
|
+
const wrapTx = this.core.hubV2.wrap(tokenOwner, leftoverAmount, CirclesType.Inflation // 1 = Inflationary
|
|
283
|
+
);
|
|
284
|
+
wrapCalls.push({
|
|
285
|
+
to: wrapTx.to,
|
|
286
|
+
data: wrapTx.data,
|
|
287
|
+
value: wrapTx.value ?? 0n,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return { unwrapCalls, wrapCalls };
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Helper method to truncate amount to 6 decimals
|
|
295
|
+
*/
|
|
296
|
+
_truncateToSixDecimals(amount) {
|
|
297
|
+
const oneMillion = BigInt(1_000_000);
|
|
298
|
+
const oneEth = BigInt(10) ** BigInt(18);
|
|
299
|
+
return (amount / (oneEth / oneMillion)) * (oneEth / oneMillion);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get default token exclusion list for transfers to group mint handlers
|
|
303
|
+
* If the recipient is a group mint handler, exclude the group token and its wrappers
|
|
304
|
+
*
|
|
305
|
+
* @param to Recipient address
|
|
306
|
+
* @param excludeFromTokens Existing token exclusion list
|
|
307
|
+
* @returns Complete token exclusion list, or undefined if empty
|
|
308
|
+
*/
|
|
309
|
+
async _getDefaultTokenExcludeList(to, excludeFromTokens) {
|
|
310
|
+
// Check if recipient is a group mint handler
|
|
311
|
+
const groups = await this.rpc.group.findGroups(1, {
|
|
312
|
+
mintHandlerEquals: to,
|
|
313
|
+
});
|
|
314
|
+
const completeExcludeFromTokenList = new Set();
|
|
315
|
+
// If recipient is a group mint handler, exclude the group's tokens
|
|
316
|
+
if (groups.length > 0) {
|
|
317
|
+
const groupInfo = groups[0];
|
|
318
|
+
completeExcludeFromTokenList.add(groupInfo.group.toLowerCase());
|
|
319
|
+
if (groupInfo.erc20WrapperDemurraged) {
|
|
320
|
+
completeExcludeFromTokenList.add(groupInfo.erc20WrapperDemurraged.toLowerCase());
|
|
321
|
+
}
|
|
322
|
+
if (groupInfo.erc20WrapperStatic) {
|
|
323
|
+
completeExcludeFromTokenList.add(groupInfo.erc20WrapperStatic.toLowerCase());
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Add any user-provided exclusions
|
|
327
|
+
excludeFromTokens?.forEach((token) => completeExcludeFromTokenList.add(token.toLowerCase()));
|
|
328
|
+
if (completeExcludeFromTokenList.size === 0) {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
return Array.from(completeExcludeFromTokenList);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransferBuilder.test.d.ts","sourceRoot":"","sources":["../src/TransferBuilder.test.ts"],"names":[],"mappings":""}
|