-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/crosschain-transactions #10
base: devop/release-1-39
Are you sure you want to change the base?
Changes from 95 commits
6cc4385
5fd1344
90dbc5d
f7e213a
385bfde
f8b06d8
6234ecc
3810cf2
5783fd8
8cf6e22
0426000
211b2a6
f329e16
eb99c7f
f4fed2e
d312277
ba05eb5
51ee5a3
59879b2
17ef97f
7b4f41d
af3c689
3eac632
fc9a983
004da65
23854d6
1a772f4
b27bce8
bbe2086
bdfa987
0e647e2
be98c2f
68962ab
7aae063
fcf7c5c
f3dfc17
64ee093
9effa84
2761ee9
02f3108
ad3fa4c
6542d90
40f5dd1
195c61a
d8812b1
46da72b
f76618c
8c82dbd
83e53ea
e68fc3c
1dcdd87
e312c96
2934a62
c55efd1
9b8888a
09dfaca
d1ae7d6
f67d4a8
d90cf63
43eeaf8
1ff72ed
6603b0e
e911972
0b6089e
8ea87b3
9bf0b9c
7b33d1e
d2130d9
aefea96
fea863d
451c318
108b64d
78079d3
da55e64
251f029
a799b50
3b25928
9b5ed94
ed21cb2
594ba2d
0d9bd98
18fb092
712e2f6
6c006e4
f1ef7c3
d9d35aa
d7affa7
de51817
9dd6f0f
21793f2
39dc07a
a45e867
8bd0e71
f88967b
943642c
99a6819
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,9 @@ import { Activity, ActivityStatus, ActivityType } from "@/types/activity"; | |
import { BaseNetwork } from "@/types/base-network"; | ||
import { NetworkEndpoints, NetworkTtls } from "./configs"; | ||
import { toBase } from "@enkryptcom/utils"; | ||
import KadenaAPI from "@/providers/kadena/libs/api"; | ||
import { ChainId, Pact } from "@kadena/client"; | ||
import { KadenaNetwork } from "@/providers/kadena/types/kadena-network"; | ||
|
||
const getAddressActivity = async ( | ||
address: string, | ||
|
@@ -26,10 +29,14 @@ export default async ( | |
network: BaseNetwork, | ||
address: string | ||
): Promise<Activity[]> => { | ||
const networkOptions = (network as KadenaNetwork).options; | ||
const networkName = network.name as keyof typeof NetworkEndpoints; | ||
const enpoint = NetworkEndpoints[networkName]; | ||
const ttl = NetworkTtls[networkName]; | ||
const activities = await getAddressActivity( | ||
const api = (await network.api()) as KadenaAPI; | ||
const chainId = await api.getChainId(); | ||
|
||
let activities = await getAddressActivity( | ||
address, | ||
enpoint, | ||
ttl, | ||
|
@@ -45,56 +52,124 @@ export default async ( | |
.then((mdata) => (price = mdata || "0")); | ||
} | ||
|
||
const groupActivities = activities.reduce((acc: any, activity: any) => { | ||
if (!acc[activity.requestKey]) { | ||
acc[activity.requestKey] = activity; | ||
} | ||
if (activity.idx === 1) { | ||
acc[activity.requestKey] = activity; | ||
} | ||
return acc; | ||
}, {}); | ||
|
||
return Object.values(groupActivities).map((activity: any, i: number) => { | ||
const rawAmount = toBase( | ||
activity.amount | ||
? parseFloat(activity.amount).toFixed(network.decimals) | ||
: "0", | ||
network.decimals | ||
); | ||
// note: intentionally not using fromAccount === some-value | ||
// I want to match both null and "" in fromAccount/toAccount | ||
// actual values will be a (truthy) string | ||
let { fromAccount, toAccount } = activity; | ||
if (!fromAccount && activity.crossChainAccount) { | ||
fromAccount = activity.crossChainAccount; | ||
} | ||
if (!toAccount && activity.crossChainAccount) { | ||
toAccount = activity.crossChainAccount; | ||
const groupActivities = activities | ||
.filter( | ||
(activity) => | ||
activity.chain == chainId || activity.crossChainId == chainId | ||
) | ||
.reduce((acc: any, activity: any) => { | ||
if (!acc[activity.requestKey]) { | ||
acc[activity.requestKey] = activity; | ||
} | ||
if (activity.idx !== 0) { | ||
acc[activity.requestKey] = activity; | ||
} | ||
return acc; | ||
}, {}); | ||
|
||
activities = Object.values(groupActivities).map( | ||
(activity: any, i: number) => { | ||
const rawAmount = toBase( | ||
activity.amount | ||
? parseFloat(activity.amount).toFixed(network.decimals) | ||
: "0", | ||
network.decimals | ||
); | ||
|
||
// note: intentionally not using fromAccount === some-value | ||
// I want to match both null and "" in fromAccount/toAccount | ||
// actual values will be a (truthy) string | ||
let { fromAccount, toAccount } = activity; | ||
if (!fromAccount && activity.crossChainAccount) { | ||
fromAccount = activity.crossChainAccount; | ||
} | ||
if (!toAccount && activity.crossChainAccount) { | ||
toAccount = activity.crossChainAccount; | ||
} | ||
|
||
return { | ||
nonce: i.toString(), | ||
from: fromAccount, | ||
to: toAccount, | ||
isIncoming: | ||
(!activity.crossChainId && fromAccount !== address) || | ||
(activity.crossChainId && activity.crossChainId === chainId), | ||
network: network.name, | ||
rawInfo: activity, | ||
chainId: activity.chain.toString(), | ||
crossChainId: activity.crossChainId, | ||
status: | ||
activity.idx !== 0 ? ActivityStatus.success : ActivityStatus.failed, | ||
timestamp: new Date(activity.blockTime).getTime(), | ||
value: rawAmount, | ||
transactionHash: activity.requestKey, | ||
type: ActivityType.transaction, | ||
token: { | ||
decimals: network.decimals, | ||
icon: network.icon, | ||
name: network.currencyNameLong, | ||
symbol: | ||
activity.token !== "coin" ? activity.token : network.currencyName, | ||
price: price, | ||
}, | ||
}; | ||
} | ||
return { | ||
nonce: i.toString(), | ||
from: fromAccount, | ||
to: toAccount, | ||
isIncoming: activity.fromAccount !== address, | ||
network: network.name, | ||
rawInfo: activity, | ||
chainId: activity.chain.toString(), | ||
crossChainId: activity.crossChainId, | ||
status: | ||
activity.idx === 1 ? ActivityStatus.success : ActivityStatus.failed, | ||
timestamp: new Date(activity.blockTime).getTime(), | ||
value: rawAmount, | ||
transactionHash: activity.requestKey, | ||
type: ActivityType.transaction, | ||
token: { | ||
decimals: network.decimals, | ||
icon: network.icon, | ||
name: network.currencyNameLong, | ||
symbol: | ||
activity.token !== "coin" ? activity.token : network.currencyName, | ||
price: price, | ||
}, | ||
}; | ||
}); | ||
); | ||
|
||
await Promise.allSettled( | ||
activities.map(async (activity: any) => { | ||
if ( | ||
activity.status === ActivityStatus.success && | ||
activity.crossChainId !== null | ||
) { | ||
Comment on lines
+120
to
+124
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it would be preferable to |
||
const fetchSpvResponse = await fetch( | ||
`${network.node}/${networkOptions.kadenaApiOptions.networkId}/chain/${activity.chainId}/pact/spv`, | ||
{ | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
requestKey: activity.transactionHash, | ||
targetChainId: String(activity.crossChainId), | ||
}), | ||
} | ||
); | ||
|
||
const spv = await fetchSpvResponse.json(); | ||
activity.spv = spv; | ||
|
||
const tx = Pact.builder | ||
.continuation({ | ||
proof: spv, | ||
data: {}, | ||
pactId: activity.transactionHash, | ||
rollback: false, | ||
step: 1, | ||
}) | ||
.setMeta({ | ||
chainId: String(activity.rawInfo.crossChainId) as ChainId, | ||
senderAccount: activity.from, | ||
}) | ||
.createTransaction(); | ||
|
||
const transactionResult = await api.sendLocalTransaction( | ||
tx, | ||
{ signatureVerification: false, preflight: false }, | ||
String(activity.rawInfo.crossChainId) as ChainId | ||
); | ||
|
||
if (transactionResult.result.status === "success") { | ||
activity.status = ActivityStatus.needs_continuation; | ||
|
||
const gasLimit = transactionResult.metaData?.publicMeta?.gasLimit; | ||
const gasPrice = transactionResult.metaData?.publicMeta?.gasPrice; | ||
const gasFee = gasLimit && gasPrice ? gasLimit * gasPrice : 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this |
||
|
||
activity.necessaryGasFeeToContinuation = gasFee; | ||
} | ||
} | ||
}) | ||
); | ||
return activities; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,7 +65,7 @@ class API implements ProviderAPIInterface { | |
async getBalanceByChainId(address: string, chainId: string): Promise<string> { | ||
const balance = await this.getBalanceAPI( | ||
this.displayAddress(address), | ||
chainId | ||
chainId.toString() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we calling |
||
); | ||
|
||
if (balance.result.status === "failure") { | ||
|
@@ -97,23 +97,49 @@ class API implements ProviderAPIInterface { | |
.setNetworkId(this.networkId) | ||
.createTransaction(); | ||
|
||
return this.dirtyRead(transaction); | ||
return this.dirtyRead(transaction, chainId); | ||
} | ||
|
||
async sendLocalTransaction( | ||
signedTranscation: ICommand | ||
signedTransaction: ICommand | IUnsignedCommand, | ||
options?: { signatureVerification: boolean; preflight: boolean }, | ||
_chainId?: string | ||
): Promise<ICommandResult> { | ||
const chainId = await this.getChainId(); | ||
const client = createClient(this.getApiHost(chainId)); | ||
return client.local(signedTranscation as ICommand); | ||
const chainId = _chainId === undefined ? await this.getChainId() : _chainId; | ||
const client = createClient( | ||
this.getApiHost(chainId ?? (await this.getChainId())) | ||
); | ||
return client.local(signedTransaction, options); | ||
} | ||
|
||
async sendTransaction( | ||
signedTranscation: ICommand | ||
): Promise<ITransactionDescriptor> { | ||
transaction: ICommand | IUnsignedCommand, | ||
chainId?: string, | ||
listen = false | ||
): Promise<{ | ||
transactionDescriptor: ITransactionDescriptor; | ||
commandResult: ICommandResult | undefined; | ||
}> { | ||
const clientChainId = chainId || (await this.getChainId()); | ||
const client = createClient(this.getApiHost(clientChainId)); | ||
const transactionDescriptor = await client.submit(transaction as ICommand); | ||
const commandResult = listen | ||
? await client.listen(transactionDescriptor) | ||
: undefined; | ||
|
||
return { transactionDescriptor, commandResult }; | ||
} | ||
|
||
async pollCreateSpv( | ||
transactionDescriptor: ITransactionDescriptor, | ||
targetChainId: string | ||
): Promise<string> { | ||
const chainId = await this.getChainId(); | ||
const client = createClient(this.getApiHost(chainId)); | ||
return client.submit(signedTranscation as ICommand); | ||
return client.pollCreateSpv( | ||
transactionDescriptor, | ||
targetChainId as ChainId | ||
); | ||
} | ||
|
||
async listen( | ||
|
@@ -125,10 +151,11 @@ class API implements ProviderAPIInterface { | |
} | ||
|
||
async dirtyRead( | ||
signedTranscation: ICommand | IUnsignedCommand | ||
signedTranscation: ICommand | IUnsignedCommand, | ||
chainId?: string | ||
): Promise<ICommandResult> { | ||
const chainId = await this.getChainId(); | ||
const client = createClient(this.getApiHost(chainId)); | ||
const clientChainId = chainId || (await this.getChainId()); | ||
const client = createClient(this.getApiHost(clientChainId)); | ||
return client.dirtyRead(signedTranscation); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can be simplified: