@ledgerhq/coin-aptos 1.9.0-next.0 → 2.0.0-nightly.3
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +36 -10
- package/lib/__tests__/api/index.test.js +61 -5
- package/lib/__tests__/api/index.test.js.map +1 -1
- package/lib/__tests__/bridge/buildTransaction.test.js +57 -2
- package/lib/__tests__/bridge/buildTransaction.test.js.map +1 -1
- package/lib/__tests__/bridge/getFeesForTransaction.test.js +109 -8
- package/lib/__tests__/bridge/getFeesForTransaction.test.js.map +1 -1
- package/lib/__tests__/bridge/getTransactionStatus.test.js +175 -37
- package/lib/__tests__/bridge/getTransactionStatus.test.js.map +1 -1
- package/lib/__tests__/bridge/logic.test.js +889 -115
- package/lib/__tests__/bridge/logic.test.js.map +1 -1
- package/lib/__tests__/bridge/signOperation.test.js +128 -2
- package/lib/__tests__/bridge/signOperation.test.js.map +1 -1
- package/lib/__tests__/bridge/synchronisation.test.js +1214 -67
- package/lib/__tests__/bridge/synchronisation.test.js.map +1 -1
- package/lib/api/graphql/queries.js +6 -6
- package/lib/api/graphql/types.d.ts +9 -9
- package/lib/api/graphql/types.d.ts.map +1 -1
- package/lib/api/index.d.ts +5 -2
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +30 -4
- package/lib/api/index.js.map +1 -1
- package/lib/bridge/bridge.fixture.d.ts +2 -0
- package/lib/bridge/bridge.fixture.d.ts.map +1 -1
- package/lib/bridge/bridge.fixture.js +85 -13
- package/lib/bridge/bridge.fixture.js.map +1 -1
- package/lib/bridge/buildTransaction.d.ts.map +1 -1
- package/lib/bridge/buildTransaction.js +29 -3
- package/lib/bridge/buildTransaction.js.map +1 -1
- package/lib/bridge/estimateMaxSpendable.js +1 -1
- package/lib/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib/bridge/getFeesForTransaction.d.ts.map +1 -1
- package/lib/bridge/getFeesForTransaction.js +12 -7
- package/lib/bridge/getFeesForTransaction.js.map +1 -1
- package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib/bridge/getTransactionStatus.js +28 -14
- package/lib/bridge/getTransactionStatus.js.map +1 -1
- package/lib/bridge/logic.d.ts +13 -8
- package/lib/bridge/logic.d.ts.map +1 -1
- package/lib/bridge/logic.js +152 -54
- package/lib/bridge/logic.js.map +1 -1
- package/lib/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib/bridge/prepareTransaction.js +2 -2
- package/lib/bridge/prepareTransaction.js.map +1 -1
- package/lib/bridge/signOperation.d.ts.map +1 -1
- package/lib/bridge/signOperation.js +17 -3
- package/lib/bridge/signOperation.js.map +1 -1
- package/lib/bridge/synchronisation.d.ts +15 -0
- package/lib/bridge/synchronisation.d.ts.map +1 -1
- package/lib/bridge/synchronisation.js +127 -4
- package/lib/bridge/synchronisation.js.map +1 -1
- package/lib/constants.d.ts +5 -1
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +6 -2
- package/lib/constants.js.map +1 -1
- package/lib/test/bot-specs.d.ts.map +1 -1
- package/lib/test/bot-specs.js +40 -1
- package/lib/test/bot-specs.js.map +1 -1
- package/lib/test/bridgeDatasetTest.d.ts.map +1 -1
- package/lib/test/bridgeDatasetTest.js +43 -62
- package/lib/test/bridgeDatasetTest.js.map +1 -1
- package/lib/test/speculos-deviceActions.d.ts +1 -0
- package/lib/test/speculos-deviceActions.d.ts.map +1 -1
- package/lib/test/speculos-deviceActions.js +37 -5
- package/lib/test/speculos-deviceActions.js.map +1 -1
- package/lib/types/index.d.ts +18 -7
- package/lib/types/index.d.ts.map +1 -1
- package/lib-es/__tests__/api/index.test.js +61 -5
- package/lib-es/__tests__/api/index.test.js.map +1 -1
- package/lib-es/__tests__/bridge/buildTransaction.test.js +58 -3
- package/lib-es/__tests__/bridge/buildTransaction.test.js.map +1 -1
- package/lib-es/__tests__/bridge/getFeesForTransaction.test.js +110 -9
- package/lib-es/__tests__/bridge/getFeesForTransaction.test.js.map +1 -1
- package/lib-es/__tests__/bridge/getTransactionStatus.test.js +177 -39
- package/lib-es/__tests__/bridge/getTransactionStatus.test.js.map +1 -1
- package/lib-es/__tests__/bridge/logic.test.js +891 -117
- package/lib-es/__tests__/bridge/logic.test.js.map +1 -1
- package/lib-es/__tests__/bridge/signOperation.test.js +128 -2
- package/lib-es/__tests__/bridge/signOperation.test.js.map +1 -1
- package/lib-es/__tests__/bridge/synchronisation.test.js +1213 -69
- package/lib-es/__tests__/bridge/synchronisation.test.js.map +1 -1
- package/lib-es/api/graphql/queries.js +6 -6
- package/lib-es/api/graphql/types.d.ts +9 -9
- package/lib-es/api/graphql/types.d.ts.map +1 -1
- package/lib-es/api/index.d.ts +5 -2
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +30 -4
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/bridge/bridge.fixture.d.ts +2 -0
- package/lib-es/bridge/bridge.fixture.d.ts.map +1 -1
- package/lib-es/bridge/bridge.fixture.js +82 -12
- package/lib-es/bridge/bridge.fixture.js.map +1 -1
- package/lib-es/bridge/buildTransaction.d.ts.map +1 -1
- package/lib-es/bridge/buildTransaction.js +30 -4
- package/lib-es/bridge/buildTransaction.js.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.js +1 -1
- package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib-es/bridge/getFeesForTransaction.d.ts.map +1 -1
- package/lib-es/bridge/getFeesForTransaction.js +13 -8
- package/lib-es/bridge/getFeesForTransaction.js.map +1 -1
- package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib-es/bridge/getTransactionStatus.js +28 -14
- package/lib-es/bridge/getTransactionStatus.js.map +1 -1
- package/lib-es/bridge/logic.d.ts +13 -8
- package/lib-es/bridge/logic.d.ts.map +1 -1
- package/lib-es/bridge/logic.js +146 -52
- package/lib-es/bridge/logic.js.map +1 -1
- package/lib-es/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib-es/bridge/prepareTransaction.js +2 -2
- package/lib-es/bridge/prepareTransaction.js.map +1 -1
- package/lib-es/bridge/signOperation.d.ts.map +1 -1
- package/lib-es/bridge/signOperation.js +17 -3
- package/lib-es/bridge/signOperation.js.map +1 -1
- package/lib-es/bridge/synchronisation.d.ts +15 -0
- package/lib-es/bridge/synchronisation.d.ts.map +1 -1
- package/lib-es/bridge/synchronisation.js +123 -3
- package/lib-es/bridge/synchronisation.js.map +1 -1
- package/lib-es/constants.d.ts +5 -1
- package/lib-es/constants.d.ts.map +1 -1
- package/lib-es/constants.js +5 -1
- package/lib-es/constants.js.map +1 -1
- package/lib-es/test/bot-specs.d.ts.map +1 -1
- package/lib-es/test/bot-specs.js +41 -2
- package/lib-es/test/bot-specs.js.map +1 -1
- package/lib-es/test/bridgeDatasetTest.d.ts.map +1 -1
- package/lib-es/test/bridgeDatasetTest.js +43 -59
- package/lib-es/test/bridgeDatasetTest.js.map +1 -1
- package/lib-es/test/speculos-deviceActions.d.ts +1 -0
- package/lib-es/test/speculos-deviceActions.d.ts.map +1 -1
- package/lib-es/test/speculos-deviceActions.js +36 -4
- package/lib-es/test/speculos-deviceActions.js.map +1 -1
- package/lib-es/types/index.d.ts +18 -7
- package/lib-es/types/index.d.ts.map +1 -1
- package/package.json +9 -8
- package/src/__tests__/api/index.test.ts +75 -5
- package/src/__tests__/bridge/buildTransaction.test.ts +85 -3
- package/src/__tests__/bridge/getFeesForTransaction.test.ts +144 -9
- package/src/__tests__/bridge/getTransactionStatus.test.ts +217 -38
- package/src/__tests__/bridge/logic.test.ts +922 -118
- package/src/__tests__/bridge/signOperation.test.ts +141 -2
- package/src/__tests__/bridge/synchronisation.test.ts +1265 -71
- package/src/api/graphql/queries.ts +6 -6
- package/src/api/graphql/types.ts +9 -9
- package/src/api/index.ts +32 -5
- package/src/bridge/bridge.fixture.ts +91 -12
- package/src/bridge/buildTransaction.ts +39 -6
- package/src/bridge/estimateMaxSpendable.ts +1 -1
- package/src/bridge/getFeesForTransaction.ts +14 -9
- package/src/bridge/getTransactionStatus.ts +35 -13
- package/src/bridge/logic.ts +202 -63
- package/src/bridge/prepareTransaction.ts +4 -3
- package/src/bridge/signOperation.ts +19 -3
- package/src/bridge/synchronisation.ts +170 -3
- package/src/constants.ts +12 -1
- package/src/test/bot-specs.ts +63 -3
- package/src/test/bridgeDatasetTest.ts +45 -59
- package/src/test/speculos-deviceActions.ts +40 -4
- package/src/types/index.ts +15 -1
package/src/bridge/logic.ts
CHANGED
|
@@ -4,20 +4,37 @@ import {
|
|
|
4
4
|
InputEntryFunctionData,
|
|
5
5
|
MoveResource,
|
|
6
6
|
WriteSetChange,
|
|
7
|
+
WriteSetChangeWriteResource,
|
|
7
8
|
} from "@aptos-labs/ts-sdk";
|
|
8
9
|
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
|
|
9
|
-
import type { Operation, OperationType } from "@ledgerhq/types-live";
|
|
10
|
+
import type { Account, Operation, OperationType, TokenAccount } from "@ledgerhq/types-live";
|
|
11
|
+
import {
|
|
12
|
+
decodeTokenAccountId,
|
|
13
|
+
encodeTokenAccountId,
|
|
14
|
+
findSubAccountById,
|
|
15
|
+
isTokenAccount,
|
|
16
|
+
} from "@ledgerhq/coin-framework/account/index";
|
|
10
17
|
import BigNumber from "bignumber.js";
|
|
11
18
|
import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
|
|
12
19
|
import {
|
|
13
|
-
|
|
20
|
+
APTOS_ASSET_ID,
|
|
21
|
+
APTOS_FUNGIBLE_STORE,
|
|
14
22
|
BATCH_TRANSFER_TYPES,
|
|
15
23
|
DELEGATION_POOL_TYPES,
|
|
16
24
|
DIRECTION,
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
COIN_TRANSFER_TYPES,
|
|
26
|
+
FA_TRANSFER_TYPES,
|
|
27
|
+
APTOS_OBJECT_CORE,
|
|
19
28
|
} from "../constants";
|
|
20
|
-
import type {
|
|
29
|
+
import type {
|
|
30
|
+
AptosFungibleoObjectCoreResourceData,
|
|
31
|
+
AptosFungibleStoreResourceData,
|
|
32
|
+
AptosMoveResource,
|
|
33
|
+
AptosTransaction,
|
|
34
|
+
Transaction,
|
|
35
|
+
TransactionOptions,
|
|
36
|
+
} from "../types";
|
|
37
|
+
import { findTokenByAddressInCurrency } from "@ledgerhq/cryptoassets";
|
|
21
38
|
|
|
22
39
|
export const DEFAULT_GAS = new BigNumber(200);
|
|
23
40
|
export const DEFAULT_GAS_PRICE = new BigNumber(100);
|
|
@@ -30,13 +47,21 @@ export function isTestnet(currencyId: string): boolean {
|
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
export const getMaxSendBalance = (
|
|
33
|
-
amount: BigNumber,
|
|
34
50
|
gas: BigNumber,
|
|
35
51
|
gasPrice: BigNumber,
|
|
52
|
+
account: Account,
|
|
53
|
+
transaction?: Transaction,
|
|
36
54
|
): BigNumber => {
|
|
55
|
+
const tokenAccount = findSubAccountById(account, transaction?.subAccountId ?? "");
|
|
56
|
+
const fromTokenAccount = tokenAccount && isTokenAccount(tokenAccount);
|
|
57
|
+
|
|
37
58
|
const totalGas = gas.multipliedBy(gasPrice);
|
|
38
59
|
|
|
39
|
-
return
|
|
60
|
+
return fromTokenAccount
|
|
61
|
+
? tokenAccount.spendableBalance
|
|
62
|
+
: account.spendableBalance.gt(totalGas)
|
|
63
|
+
? account.spendableBalance.minus(totalGas)
|
|
64
|
+
: new BigNumber(0);
|
|
40
65
|
};
|
|
41
66
|
|
|
42
67
|
export function normalizeTransactionOptions(options: TransactionOptions): TransactionOptions {
|
|
@@ -88,9 +113,10 @@ export const txsToOps = (
|
|
|
88
113
|
info: { address: string },
|
|
89
114
|
id: string,
|
|
90
115
|
txs: (AptosTransaction | null)[],
|
|
91
|
-
): Operation[] => {
|
|
116
|
+
): [Operation[], Operation[]] => {
|
|
92
117
|
const { address } = info;
|
|
93
118
|
const ops: Operation[] = [];
|
|
119
|
+
const opsTokens: Operation[] = [];
|
|
94
120
|
|
|
95
121
|
txs.forEach(tx => {
|
|
96
122
|
if (tx !== null) {
|
|
@@ -107,10 +133,12 @@ export const txsToOps = (
|
|
|
107
133
|
return; // skip transaction without functions in payload
|
|
108
134
|
}
|
|
109
135
|
|
|
110
|
-
const { amount_in, amount_out } =
|
|
111
|
-
op.value = calculateAmount(tx.sender, address,
|
|
136
|
+
const { coin_id, amount_in, amount_out } = getCoinAndAmounts(tx, address);
|
|
137
|
+
op.value = calculateAmount(tx.sender, address, amount_in, amount_out);
|
|
112
138
|
op.type = compareAddress(tx.sender, address) ? DIRECTION.OUT : DIRECTION.IN;
|
|
113
139
|
op.senders.push(tx.sender);
|
|
140
|
+
op.hasFailed = !tx.success;
|
|
141
|
+
op.id = encodeOperationId(op.accountId, tx.hash, op.type);
|
|
114
142
|
|
|
115
143
|
processRecipients(payload, address, op, function_address);
|
|
116
144
|
|
|
@@ -119,13 +147,30 @@ export const txsToOps = (
|
|
|
119
147
|
op.type = DIRECTION.UNKNOWN;
|
|
120
148
|
}
|
|
121
149
|
|
|
122
|
-
op.
|
|
123
|
-
|
|
124
|
-
|
|
150
|
+
if (op.type !== DIRECTION.UNKNOWN && coin_id !== null) {
|
|
151
|
+
if (coin_id === APTOS_ASSET_ID) {
|
|
152
|
+
ops.push(op);
|
|
153
|
+
} else {
|
|
154
|
+
const token = findTokenByAddressInCurrency(coin_id.toLowerCase(), "aptos");
|
|
155
|
+
if (token !== undefined) {
|
|
156
|
+
op.accountId = encodeTokenAccountId(id, token);
|
|
157
|
+
opsTokens.push(op);
|
|
158
|
+
|
|
159
|
+
if (op.type === DIRECTION.OUT) {
|
|
160
|
+
ops.push({
|
|
161
|
+
...op,
|
|
162
|
+
accountId: decodeTokenAccountId(op.accountId).accountId,
|
|
163
|
+
value: op.fee,
|
|
164
|
+
type: "FEES",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
125
170
|
}
|
|
126
171
|
});
|
|
127
172
|
|
|
128
|
-
return ops;
|
|
173
|
+
return [ops, opsTokens];
|
|
129
174
|
};
|
|
130
175
|
|
|
131
176
|
export function compareAddress(addressA: string, addressB: string) {
|
|
@@ -151,7 +196,7 @@ export function processRecipients(
|
|
|
151
196
|
): void {
|
|
152
197
|
// get recipients buy 3 groups
|
|
153
198
|
if (
|
|
154
|
-
(
|
|
199
|
+
(COIN_TRANSFER_TYPES.includes(payload.function) ||
|
|
155
200
|
DELEGATION_POOL_TYPES.includes(payload.function)) &&
|
|
156
201
|
payload.functionArguments &&
|
|
157
202
|
payload.functionArguments.length > 0 &&
|
|
@@ -159,6 +204,15 @@ export function processRecipients(
|
|
|
159
204
|
) {
|
|
160
205
|
// 1. Transfer like functions (includes some delegation pool functions)
|
|
161
206
|
op.recipients.push(payload.functionArguments[0].toString());
|
|
207
|
+
} else if (
|
|
208
|
+
FA_TRANSFER_TYPES.includes(payload.function) &&
|
|
209
|
+
payload.functionArguments &&
|
|
210
|
+
payload.functionArguments.length > 1 &&
|
|
211
|
+
typeof payload.functionArguments[0] === "object" &&
|
|
212
|
+
typeof payload.functionArguments[1] === "string"
|
|
213
|
+
) {
|
|
214
|
+
// 1. Transfer like functions (includes some delegation pool functions)
|
|
215
|
+
op.recipients.push(payload.functionArguments[1].toString());
|
|
162
216
|
} else if (
|
|
163
217
|
BATCH_TRANSFER_TYPES.includes(payload.function) &&
|
|
164
218
|
payload.functionArguments &&
|
|
@@ -179,87 +233,172 @@ export function processRecipients(
|
|
|
179
233
|
}
|
|
180
234
|
}
|
|
181
235
|
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
return isChangeOfAptos(change, event, event_name);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function isChangeOfAptos(
|
|
189
|
-
writeSetChange: WriteSetChange,
|
|
236
|
+
export function getEventCoinAddress(
|
|
237
|
+
change: WriteSetChangeWriteResource,
|
|
190
238
|
event: Event,
|
|
191
239
|
event_name: string,
|
|
192
|
-
):
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
240
|
+
): string | null {
|
|
241
|
+
const change_data = change.data;
|
|
242
|
+
|
|
243
|
+
const mr = change_data as MoveResource<AptosMoveResource>; // -> this is data that we want to parse
|
|
244
|
+
|
|
245
|
+
if (!(event_name in mr.data)) {
|
|
246
|
+
return null;
|
|
197
247
|
}
|
|
198
248
|
|
|
199
|
-
|
|
200
|
-
|
|
249
|
+
const change_event_data = mr.data[event_name];
|
|
250
|
+
if (
|
|
251
|
+
change_event_data.guid.id.addr !== event.guid.account_address ||
|
|
252
|
+
change_event_data.guid.id.creation_num !== event.guid.creation_number
|
|
253
|
+
) {
|
|
254
|
+
return null;
|
|
201
255
|
}
|
|
202
256
|
|
|
203
|
-
const
|
|
257
|
+
const address = extractAddress(mr.type);
|
|
258
|
+
|
|
259
|
+
return address;
|
|
260
|
+
}
|
|
204
261
|
|
|
205
|
-
|
|
206
|
-
|
|
262
|
+
export function getEventFAAddress(
|
|
263
|
+
change: WriteSetChangeWriteResource,
|
|
264
|
+
event: Event,
|
|
265
|
+
_event_name: string,
|
|
266
|
+
): string | null {
|
|
267
|
+
const change_data = change.data;
|
|
268
|
+
|
|
269
|
+
if (change_data.type !== APTOS_FUNGIBLE_STORE) {
|
|
270
|
+
return null;
|
|
207
271
|
}
|
|
208
272
|
|
|
209
|
-
const mr = change_data as MoveResource<
|
|
273
|
+
const mr = change_data as MoveResource<AptosFungibleStoreResourceData>;
|
|
210
274
|
|
|
211
|
-
if (
|
|
212
|
-
return
|
|
275
|
+
if (change.address !== event.data.store) {
|
|
276
|
+
return null;
|
|
213
277
|
}
|
|
214
278
|
|
|
215
|
-
|
|
279
|
+
return mr.data.metadata.inner;
|
|
280
|
+
}
|
|
216
281
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
282
|
+
export function getResourceAddress(
|
|
283
|
+
tx: AptosTransaction,
|
|
284
|
+
event: Event,
|
|
285
|
+
event_name: string,
|
|
286
|
+
getAddressProcessor: (
|
|
287
|
+
change: WriteSetChangeWriteResource,
|
|
288
|
+
event: Event,
|
|
289
|
+
event_name: string,
|
|
290
|
+
) => string | null,
|
|
291
|
+
): string | null {
|
|
292
|
+
for (const change of tx.changes) {
|
|
293
|
+
if (isWriteSetChangeWriteResource(change)) {
|
|
294
|
+
const address = getAddressProcessor(change, event, event_name);
|
|
295
|
+
if (address !== null) {
|
|
296
|
+
return address;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isWriteSetChangeWriteResource(
|
|
304
|
+
change: WriteSetChange,
|
|
305
|
+
): change is WriteSetChangeWriteResource {
|
|
306
|
+
return (change as WriteSetChangeWriteResource).data !== undefined;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function checkFAOwner(tx: AptosTransaction, event: Event, user_address: string): boolean {
|
|
310
|
+
for (const change of tx.changes) {
|
|
311
|
+
if (isWriteSetChangeWriteResource(change)) {
|
|
312
|
+
const storeData = change.data as MoveResource<AptosFungibleoObjectCoreResourceData>;
|
|
313
|
+
if (
|
|
314
|
+
change.address == event.data.store &&
|
|
315
|
+
storeData.type == APTOS_OBJECT_CORE &&
|
|
316
|
+
storeData.data.owner == user_address
|
|
317
|
+
) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return false;
|
|
221
323
|
}
|
|
222
324
|
|
|
223
|
-
export function
|
|
325
|
+
export function getCoinAndAmounts(
|
|
224
326
|
tx: AptosTransaction,
|
|
225
327
|
address: string,
|
|
226
|
-
): { amount_in: BigNumber; amount_out: BigNumber } {
|
|
227
|
-
let
|
|
228
|
-
let
|
|
328
|
+
): { coin_id: string | null; amount_in: BigNumber; amount_out: BigNumber } {
|
|
329
|
+
let coin_id: string | null = null;
|
|
330
|
+
let amount_in = BigNumber(0);
|
|
331
|
+
let amount_out = BigNumber(0);
|
|
332
|
+
|
|
229
333
|
// collect all events related to the address and calculate the overall amounts
|
|
230
334
|
tx.events.forEach(event => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
335
|
+
switch (event.type) {
|
|
336
|
+
case "0x1::coin::WithdrawEvent":
|
|
337
|
+
if (compareAddress(event.guid.account_address, address)) {
|
|
338
|
+
coin_id = getResourceAddress(tx, event, "withdraw_events", getEventCoinAddress);
|
|
339
|
+
amount_out = amount_out.plus(event.data.amount);
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
342
|
+
case "0x1::coin::DepositEvent":
|
|
343
|
+
if (compareAddress(event.guid.account_address, address)) {
|
|
344
|
+
coin_id = getResourceAddress(tx, event, "deposit_events", getEventCoinAddress);
|
|
345
|
+
amount_in = amount_in.plus(event.data.amount);
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
case "0x1::fungible_asset::Withdraw":
|
|
349
|
+
if (checkFAOwner(tx, event, address)) {
|
|
350
|
+
coin_id = getResourceAddress(tx, event, "withdraw_events", getEventFAAddress);
|
|
351
|
+
amount_out = amount_out.plus(event.data.amount);
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
case "0x1::fungible_asset::Deposit":
|
|
355
|
+
if (checkFAOwner(tx, event, address)) {
|
|
356
|
+
coin_id = getResourceAddress(tx, event, "deposit_events", getEventFAAddress);
|
|
357
|
+
amount_in = amount_in.plus(event.data.amount);
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
case "0x1::transaction_fee::FeeStatement":
|
|
361
|
+
if (tx.sender === address) {
|
|
362
|
+
if (coin_id === null) coin_id = APTOS_ASSET_ID;
|
|
363
|
+
if (coin_id === APTOS_ASSET_ID) {
|
|
364
|
+
const fees = BigNumber(tx.gas_unit_price).times(BigNumber(tx.gas_used));
|
|
365
|
+
amount_out = amount_out.plus(fees);
|
|
241
366
|
}
|
|
242
|
-
|
|
243
|
-
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
244
369
|
}
|
|
245
370
|
});
|
|
246
|
-
return { amount_in, amount_out };
|
|
371
|
+
return { coin_id, amount_in, amount_out }; // TODO: manage situation when there are several coinID from the events parsing
|
|
247
372
|
}
|
|
248
373
|
|
|
249
374
|
export function calculateAmount(
|
|
250
375
|
sender: string,
|
|
251
376
|
address: string,
|
|
252
|
-
fee: BigNumber,
|
|
253
377
|
amount_in: BigNumber,
|
|
254
378
|
amount_out: BigNumber,
|
|
255
379
|
): BigNumber {
|
|
256
380
|
const is_sender: boolean = compareAddress(sender, address);
|
|
257
|
-
// Include fees if our address is the sender
|
|
258
|
-
if (is_sender) {
|
|
259
|
-
amount_out = amount_out.plus(fee);
|
|
260
|
-
}
|
|
261
381
|
// LL negates the amount for SEND transactions
|
|
262
382
|
// to show positive amount on the send transaction (ex: in "cancel" tx, when amount will be returned to our account)
|
|
263
383
|
// we need to make it negative
|
|
264
384
|
return is_sender ? amount_out.minus(amount_in) : amount_in.minus(amount_out);
|
|
265
385
|
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Extracts the address from a string like "0x1::coin::CoinStore<address::module::type>"
|
|
389
|
+
* @param {string} str - The input string containing the address.
|
|
390
|
+
* @returns {string | null} - The extracted address or null if not found.
|
|
391
|
+
*/
|
|
392
|
+
function extractAddress(str: string): string | null {
|
|
393
|
+
const match = str.match(/<([^<>]+)>{1}$/);
|
|
394
|
+
return match ? match[1] : null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function getTokenAccount(
|
|
398
|
+
account: Account,
|
|
399
|
+
transaction: Transaction,
|
|
400
|
+
): TokenAccount | undefined {
|
|
401
|
+
const tokenAccount = findSubAccountById(account, transaction.subAccountId ?? "");
|
|
402
|
+
const fromTokenAccount = tokenAccount && isTokenAccount(tokenAccount);
|
|
403
|
+
return fromTokenAccount ? tokenAccount : undefined;
|
|
404
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Account } from "@ledgerhq/types-live";
|
|
2
2
|
import BigNumber from "bignumber.js";
|
|
3
|
-
|
|
4
3
|
import { AptosAPI } from "../api";
|
|
5
4
|
import { getEstimatedGas } from "./getFeesForTransaction";
|
|
6
5
|
import type { Transaction } from "../types";
|
|
@@ -28,9 +27,10 @@ const prepareTransaction = async (
|
|
|
28
27
|
if (transaction.useAllAmount) {
|
|
29
28
|
// we will use this amount in simulation, to estimate gas
|
|
30
29
|
transaction.amount = getMaxSendBalance(
|
|
31
|
-
account.spendableBalance,
|
|
32
30
|
new BigNumber(DEFAULT_GAS),
|
|
33
31
|
new BigNumber(DEFAULT_GAS_PRICE),
|
|
32
|
+
account,
|
|
33
|
+
transaction,
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -39,9 +39,10 @@ const prepareTransaction = async (
|
|
|
39
39
|
if (transaction.useAllAmount) {
|
|
40
40
|
// correct the transaction amount according to estimated fees
|
|
41
41
|
transaction.amount = getMaxSendBalance(
|
|
42
|
-
account.spendableBalance,
|
|
43
42
|
BigNumber(estimate.maxGasAmount),
|
|
44
43
|
BigNumber(estimate.gasUnitPrice),
|
|
44
|
+
account,
|
|
45
|
+
transaction,
|
|
45
46
|
);
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -9,6 +9,7 @@ import { AptosAPI } from "../api";
|
|
|
9
9
|
import { SignerContext } from "@ledgerhq/coin-framework/signer";
|
|
10
10
|
import { AptosSigner } from "../types";
|
|
11
11
|
import { signTransaction } from "../network";
|
|
12
|
+
import { findSubAccountById } from "@ledgerhq/coin-framework/account/helpers";
|
|
12
13
|
|
|
13
14
|
export const getAddress = (a: Account) => ({
|
|
14
15
|
address: a.freshAddress,
|
|
@@ -45,14 +46,15 @@ const buildSignOperation =
|
|
|
45
46
|
recipients.push(transaction.recipient);
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
const subAccount =
|
|
50
|
+
!!transaction.subAccountId && findSubAccountById(account, transaction.subAccountId);
|
|
51
|
+
|
|
48
52
|
// build optimistic operation
|
|
49
53
|
const operation: Operation = {
|
|
50
54
|
id: encodeOperationId(accountId, hash, type),
|
|
51
55
|
hash,
|
|
52
56
|
type,
|
|
53
|
-
value: transaction.
|
|
54
|
-
? account.balance.minus(fee)
|
|
55
|
-
: transaction.amount.plus(fee),
|
|
57
|
+
value: subAccount ? fee : transaction.amount.plus(fee),
|
|
56
58
|
fee,
|
|
57
59
|
extra,
|
|
58
60
|
blockHash: null,
|
|
@@ -62,6 +64,20 @@ const buildSignOperation =
|
|
|
62
64
|
accountId,
|
|
63
65
|
date: new Date(),
|
|
64
66
|
transactionSequenceNumber: Number(rawTx.sequence_number),
|
|
67
|
+
subOperations: subAccount
|
|
68
|
+
? [
|
|
69
|
+
{
|
|
70
|
+
id: encodeOperationId(subAccount.id, "", "OUT"),
|
|
71
|
+
type: "OUT",
|
|
72
|
+
accountId: transaction.subAccountId,
|
|
73
|
+
senders: [account.freshAddress],
|
|
74
|
+
recipients: [transaction.recipient],
|
|
75
|
+
value: transaction.amount,
|
|
76
|
+
fee,
|
|
77
|
+
date: new Date(),
|
|
78
|
+
} as Operation,
|
|
79
|
+
]
|
|
80
|
+
: [],
|
|
65
81
|
};
|
|
66
82
|
|
|
67
83
|
o.next({
|
|
@@ -1,9 +1,160 @@
|
|
|
1
|
+
import { inferSubOperations } from "@ledgerhq/coin-framework/serialization/index";
|
|
1
2
|
import { decodeAccountId, encodeAccountId } from "@ledgerhq/coin-framework/account";
|
|
2
3
|
import type { GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
3
4
|
import { mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
4
5
|
import { AptosAPI } from "../api";
|
|
5
6
|
import { txsToOps } from "./logic";
|
|
6
7
|
import type { AptosAccount } from "../types";
|
|
8
|
+
import { Account, Operation, TokenAccount } from "@ledgerhq/types-live";
|
|
9
|
+
import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
|
|
10
|
+
import {
|
|
11
|
+
decodeTokenAccountId,
|
|
12
|
+
emptyHistoryCache,
|
|
13
|
+
encodeTokenAccountId,
|
|
14
|
+
} from "@ledgerhq/coin-framework/account/index";
|
|
15
|
+
import { AccountShapeInfo } from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* List of properties of a sub account that can be updated when 2 "identical" accounts are found
|
|
19
|
+
*/
|
|
20
|
+
const updatableSubAccountProperties: { name: string; isOps: boolean }[] = [
|
|
21
|
+
{ name: "balance", isOps: false },
|
|
22
|
+
{ name: "spendableBalance", isOps: false },
|
|
23
|
+
{ name: "balanceHistoryCache", isOps: false },
|
|
24
|
+
{ name: "operations", isOps: true },
|
|
25
|
+
{ name: "pendingOperations", isOps: true },
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* In charge of smartly merging sub accounts while maintaining references as much as possible
|
|
29
|
+
*/
|
|
30
|
+
export const mergeSubAccounts = (
|
|
31
|
+
initialAccount: Account | undefined,
|
|
32
|
+
newSubAccounts: TokenAccount[],
|
|
33
|
+
): Array<TokenAccount> => {
|
|
34
|
+
const oldSubAccounts: Array<TokenAccount> | undefined = initialAccount?.subAccounts;
|
|
35
|
+
if (!oldSubAccounts) {
|
|
36
|
+
return newSubAccounts;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Creating a map of already existing sub accounts by id
|
|
40
|
+
const oldSubAccountsById: { [key: string]: TokenAccount } = {};
|
|
41
|
+
for (const oldSubAccount of oldSubAccounts) {
|
|
42
|
+
oldSubAccountsById[oldSubAccount.id!] = oldSubAccount;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Looping on new sub accounts to compare them with already existing ones
|
|
46
|
+
// Already existing will be updated if necessary (see `updatableSubAccountProperties`)
|
|
47
|
+
// Fresh new sub accounts will be added/pushed after already existing
|
|
48
|
+
const newSubAccountsToAdd: TokenAccount[] = [];
|
|
49
|
+
for (const newSubAccount of newSubAccounts) {
|
|
50
|
+
const duplicatedAccount: TokenAccount | undefined = oldSubAccountsById[newSubAccount.id!];
|
|
51
|
+
|
|
52
|
+
// If this sub account was not already in the initialAccount
|
|
53
|
+
if (!duplicatedAccount) {
|
|
54
|
+
// We'll add it later
|
|
55
|
+
newSubAccountsToAdd.push(newSubAccount);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const updates: Partial<TokenAccount> & { [key: string]: any } = {};
|
|
60
|
+
for (const { name, isOps } of updatableSubAccountProperties) {
|
|
61
|
+
if (!isOps) {
|
|
62
|
+
if (
|
|
63
|
+
newSubAccount[name as keyof TokenAccount] !==
|
|
64
|
+
duplicatedAccount[name as keyof TokenAccount]
|
|
65
|
+
) {
|
|
66
|
+
updates[name] = newSubAccount[name as keyof TokenAccount];
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
updates[name] =
|
|
70
|
+
mergeOps(
|
|
71
|
+
duplicatedAccount[name as keyof TokenAccount] as Operation[],
|
|
72
|
+
newSubAccount[name as keyof TokenAccount] as Operation[],
|
|
73
|
+
) || [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Updating the operationsCount in case the mergeOps changed it
|
|
78
|
+
updates.operationsCount =
|
|
79
|
+
updates.operations?.length || duplicatedAccount?.operations?.length || 0;
|
|
80
|
+
|
|
81
|
+
// Modifying the Map with the updated sub account with a new ref
|
|
82
|
+
oldSubAccountsById[newSubAccount.id!] = {
|
|
83
|
+
...duplicatedAccount,
|
|
84
|
+
...updates,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const updatedSubAccounts = Object.values(oldSubAccountsById);
|
|
88
|
+
return [...updatedSubAccounts, ...newSubAccountsToAdd];
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Fetch the balance for a token and creates a TokenAccount based on this and the provided operations
|
|
93
|
+
*/
|
|
94
|
+
export const getSubAccountShape = async (
|
|
95
|
+
currency: CryptoCurrency,
|
|
96
|
+
address: string,
|
|
97
|
+
parentId: string,
|
|
98
|
+
token: TokenCurrency,
|
|
99
|
+
operations: Operation[],
|
|
100
|
+
): Promise<TokenAccount> => {
|
|
101
|
+
const aptosClient = new AptosAPI(currency.id);
|
|
102
|
+
const tokenAccountId = encodeTokenAccountId(parentId, token);
|
|
103
|
+
const balance = await aptosClient.getBalance(address, token);
|
|
104
|
+
const firstOperation = operations
|
|
105
|
+
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
|
106
|
+
.at(operations.length - 1);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
type: "TokenAccount",
|
|
110
|
+
id: tokenAccountId,
|
|
111
|
+
parentId,
|
|
112
|
+
token,
|
|
113
|
+
balance,
|
|
114
|
+
spendableBalance: balance,
|
|
115
|
+
creationDate: firstOperation?.date || new Date(0),
|
|
116
|
+
operations,
|
|
117
|
+
operationsCount: operations.length,
|
|
118
|
+
pendingOperations: [],
|
|
119
|
+
balanceHistoryCache: emptyHistoryCache,
|
|
120
|
+
swapHistory: [],
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Getting all token related operations in order to provide TokenAccounts
|
|
126
|
+
*/
|
|
127
|
+
export const getSubAccounts = async (
|
|
128
|
+
infos: AccountShapeInfo<Account>,
|
|
129
|
+
address: string,
|
|
130
|
+
accountId: string,
|
|
131
|
+
lastTokenOperations: Operation[],
|
|
132
|
+
): Promise<TokenAccount[]> => {
|
|
133
|
+
const { currency } = infos;
|
|
134
|
+
|
|
135
|
+
// Creating a Map of Operations by TokenCurrencies in order to know which TokenAccounts should be synced as well
|
|
136
|
+
const operationsByToken = lastTokenOperations.reduce<Map<TokenCurrency, Operation[]>>(
|
|
137
|
+
(acc, operation) => {
|
|
138
|
+
const { token } = decodeTokenAccountId(operation.accountId);
|
|
139
|
+
if (!token) return acc; // TODO: do we need to check blacklistedTokenIds
|
|
140
|
+
|
|
141
|
+
if (!acc.has(token)) {
|
|
142
|
+
acc.set(token, []);
|
|
143
|
+
}
|
|
144
|
+
acc.get(token)?.push(operation);
|
|
145
|
+
return acc;
|
|
146
|
+
},
|
|
147
|
+
new Map<TokenCurrency, Operation[]>(),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Fetching all TokenAccounts possible and providing already filtered operations
|
|
151
|
+
const subAccountsPromises: Promise<TokenAccount>[] = [];
|
|
152
|
+
for (const [token, ops] of operationsByToken.entries()) {
|
|
153
|
+
subAccountsPromises.push(getSubAccountShape(currency, address, accountId, token, ops));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return Promise.all(subAccountsPromises);
|
|
157
|
+
};
|
|
7
158
|
|
|
8
159
|
export const getAccountShape: GetAccountShape = async info => {
|
|
9
160
|
const { address, initialAccount, currency, derivationMode, rest } = info;
|
|
@@ -26,14 +177,29 @@ export const getAccountShape: GetAccountShape = async info => {
|
|
|
26
177
|
const xpub = initialAccount?.xpub || publicKey || "";
|
|
27
178
|
|
|
28
179
|
const oldOperations = initialAccount?.operations || [];
|
|
29
|
-
const startAt = (oldOperations[0]?.extra as any)?.version;
|
|
30
180
|
|
|
31
181
|
const aptosClient = new AptosAPI(currency.id);
|
|
32
|
-
const { balance, transactions, blockHeight } = await aptosClient.getAccountInfo(address
|
|
182
|
+
const { balance, transactions, blockHeight } = await aptosClient.getAccountInfo(address);
|
|
33
183
|
|
|
34
|
-
const newOperations = txsToOps(
|
|
184
|
+
const [newOperations, tokenOperations]: [Operation[], Operation[]] = txsToOps(
|
|
185
|
+
info,
|
|
186
|
+
accountId,
|
|
187
|
+
transactions,
|
|
188
|
+
);
|
|
35
189
|
const operations = mergeOps(oldOperations, newOperations);
|
|
36
190
|
|
|
191
|
+
const newSubAccounts = await getSubAccounts(info, address, accountId, tokenOperations);
|
|
192
|
+
const shouldSyncFromScratch = initialAccount === undefined;
|
|
193
|
+
const subAccounts = shouldSyncFromScratch
|
|
194
|
+
? newSubAccounts
|
|
195
|
+
: mergeSubAccounts(initialAccount, newSubAccounts);
|
|
196
|
+
|
|
197
|
+
operations.forEach(op => {
|
|
198
|
+
const subOperations = inferSubOperations(op.hash, subAccounts);
|
|
199
|
+
op.subOperations =
|
|
200
|
+
subOperations.length === 1 ? subOperations : subOperations.filter(op => !!op.blockHash);
|
|
201
|
+
});
|
|
202
|
+
|
|
37
203
|
const shape: Partial<AptosAccount> = {
|
|
38
204
|
type: "Account",
|
|
39
205
|
id: accountId,
|
|
@@ -44,6 +210,7 @@ export const getAccountShape: GetAccountShape = async info => {
|
|
|
44
210
|
operationsCount: operations.length,
|
|
45
211
|
blockHeight,
|
|
46
212
|
lastSyncDate: new Date(),
|
|
213
|
+
subAccounts,
|
|
47
214
|
};
|
|
48
215
|
|
|
49
216
|
return shape;
|
package/src/constants.ts
CHANGED
|
@@ -10,25 +10,36 @@ export enum TX_STATUS {
|
|
|
10
10
|
|
|
11
11
|
export const WRITE_RESOURCE = "write_resource";
|
|
12
12
|
|
|
13
|
-
export const
|
|
13
|
+
export const COIN_TRANSFER_TYPES: MoveStructId[] = [
|
|
14
14
|
"0x1::aptos_account::transfer",
|
|
15
15
|
"0x1::aptos_account::transfer_coins",
|
|
16
16
|
"0x1::coin::transfer",
|
|
17
17
|
];
|
|
18
|
+
|
|
19
|
+
export const FA_TRANSFER_TYPES: MoveStructId[] = ["0x1::primary_fungible_store::transfer"];
|
|
20
|
+
|
|
18
21
|
export const BATCH_TRANSFER_TYPES: MoveStructId[] = [
|
|
19
22
|
"0x1::aptos_account::batch_transfer",
|
|
20
23
|
"0x1::aptos_account::batch_transfer_coins",
|
|
21
24
|
];
|
|
25
|
+
|
|
22
26
|
export const DELEGATION_POOL_TYPES: MoveStructId[] = [
|
|
23
27
|
"0x1::delegation_pool::add_stake",
|
|
24
28
|
"0x1::delegation_pool::withdraw",
|
|
25
29
|
];
|
|
26
30
|
|
|
27
31
|
export const APTOS_ASSET_ID: MoveStructId = "0x1::aptos_coin::AptosCoin";
|
|
32
|
+
|
|
28
33
|
export const APTOS_COIN_CHANGE: MoveStructId = `0x1::coin::CoinStore<${APTOS_ASSET_ID}>`;
|
|
29
34
|
|
|
35
|
+
export const APTOS_FUNGIBLE_STORE: MoveStructId = "0x1::fungible_asset::FungibleStore";
|
|
36
|
+
|
|
37
|
+
export const APTOS_OBJECT_CORE: MoveStructId = "0x1::object::ObjectCore";
|
|
38
|
+
|
|
30
39
|
export enum DIRECTION {
|
|
31
40
|
IN = "IN",
|
|
32
41
|
OUT = "OUT",
|
|
33
42
|
UNKNOWN = "UNKNOWN",
|
|
34
43
|
}
|
|
44
|
+
|
|
45
|
+
export const SUPPORTED_TOKEN_TYPES = ["coin", "fungible_asset"];
|