-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
680 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
node_modules | ||
.DS_Store | ||
dist | ||
#dist | ||
*.local | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './src/SwapHandler'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './src/SwapHandler'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import type { ConsensusState, TransactionDetails } from '@nimiq/electrum-client'; | ||
import { AssetAdapter, SwapAsset } from './IAssetAdapter'; | ||
export { ConsensusState, TransactionDetails }; | ||
export interface BitcoinClient { | ||
addTransactionListener(listener: (tx: TransactionDetails) => any, addresses: string[]): number | Promise<number>; | ||
getTransactionsByAddress(address: string, sinceBlockHeight?: number, knownTransactions?: TransactionDetails[]): Promise<TransactionDetails[]>; | ||
removeListener(handle: number): void | Promise<void>; | ||
sendTransaction(tx: TransactionDetails | string): Promise<TransactionDetails>; | ||
addConsensusChangedListener(listener: (consensusState: ConsensusState) => any): number | Promise<number>; | ||
} | ||
export declare class BitcoinAssetAdapter implements AssetAdapter<SwapAsset.BTC> { | ||
client: BitcoinClient; | ||
private cancelCallback; | ||
private stopped; | ||
constructor(client: BitcoinClient); | ||
private findTransaction; | ||
awaitHtlcFunding(address: string, value: number, data?: string, confirmations?: number, onPending?: (tx: TransactionDetails) => any): Promise<TransactionDetails>; | ||
fundHtlc(serializedTx: string): Promise<TransactionDetails>; | ||
awaitHtlcSettlement(address: string, data: string): Promise<TransactionDetails>; | ||
awaitSwapSecret(address: string, data: string): Promise<string>; | ||
settleHtlc(serializedTx: string, secret: string): Promise<TransactionDetails>; | ||
awaitSettlementConfirmation(address: string, onUpdate?: (tx: TransactionDetails) => any): Promise<TransactionDetails>; | ||
stop(reason: Error): void; | ||
private sendTransaction; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
export class BitcoinAssetAdapter { | ||
constructor(client) { | ||
this.client = client; | ||
this.cancelCallback = null; | ||
this.stopped = false; | ||
} | ||
async findTransaction(address, test) { | ||
return new Promise(async (resolve, reject) => { | ||
const listener = (tx) => { | ||
if (!test(tx)) | ||
return false; | ||
cleanup(); | ||
resolve(tx); | ||
return true; | ||
}; | ||
const transactionListener = await this.client.addTransactionListener(listener, [address]); | ||
let history = []; | ||
const checkHistory = async () => { | ||
history = await this.client.getTransactionsByAddress(address, 0, history); | ||
for (const tx of history) { | ||
if (listener(tx)) | ||
break; | ||
} | ||
}; | ||
checkHistory(); | ||
const consensusListener = await this.client.addConsensusChangedListener((consensusState) => consensusState === 'established' && checkHistory()); | ||
const historyCheckInterval = window.setInterval(checkHistory, 60 * 1000); | ||
const cleanup = () => { | ||
this.client.removeListener(transactionListener); | ||
this.client.removeListener(consensusListener); | ||
window.clearInterval(historyCheckInterval); | ||
this.cancelCallback = null; | ||
}; | ||
this.cancelCallback = (reason) => { | ||
cleanup(); | ||
reject(reason); | ||
}; | ||
}); | ||
} | ||
async awaitHtlcFunding(address, value, data, confirmations = 0, onPending) { | ||
return this.findTransaction(address, (tx) => { | ||
const htlcOutput = tx.outputs.find((out) => out.address === address); | ||
if (!htlcOutput) | ||
return false; | ||
if (htlcOutput.value !== value) | ||
return false; | ||
if (tx.confirmations < confirmations) { | ||
if (typeof onPending === 'function') | ||
onPending(tx); | ||
return false; | ||
} | ||
if (tx.replaceByFee) { | ||
if (tx.state === 'mined' || tx.state === 'confirmed') | ||
return true; | ||
if (typeof onPending === 'function') | ||
onPending(tx); | ||
return false; | ||
} | ||
return true; | ||
}); | ||
} | ||
async fundHtlc(serializedTx) { | ||
if (this.stopped) | ||
throw new Error('BitcoinAssetAdapter called while stopped'); | ||
return this.sendTransaction(serializedTx); | ||
} | ||
async awaitHtlcSettlement(address, data) { | ||
return this.findTransaction(address, (tx) => tx.inputs.some((input) => input.address === address | ||
&& typeof input.witness[4] === 'string' && input.witness[4] === data)); | ||
} | ||
async awaitSwapSecret(address, data) { | ||
const tx = await this.awaitHtlcSettlement(address, data); | ||
return tx.inputs[0].witness[2]; | ||
} | ||
async settleHtlc(serializedTx, secret) { | ||
serializedTx = serializedTx.replace('000000000000000000000000000000000000000000000000000000000000000001', `${secret}01`); | ||
return this.sendTransaction(serializedTx); | ||
} | ||
async awaitSettlementConfirmation(address, onUpdate) { | ||
return this.findTransaction(address, (tx) => { | ||
if (!tx.inputs.some((input) => input.address === address && input.witness.length === 5)) | ||
return false; | ||
if (tx.state === 'mined' || tx.state === 'confirmed') | ||
return true; | ||
if (typeof onUpdate === 'function') | ||
onUpdate(tx); | ||
return false; | ||
}); | ||
} | ||
stop(reason) { | ||
if (this.cancelCallback) | ||
this.cancelCallback(reason); | ||
this.stopped = true; | ||
} | ||
async sendTransaction(serializedTx) { | ||
return this.client.sendTransaction(serializedTx); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { BigNumber, Contract, Event as EthersEvent, EventFilter } from 'ethers'; | ||
import { AssetAdapter, SwapAsset } from './IAssetAdapter'; | ||
export declare enum EventType { | ||
OPEN = "Open", | ||
REDEEM = "Redeem", | ||
REFUND = "Refund" | ||
} | ||
type OpenEventArgs = [ | ||
string, | ||
string, | ||
BigNumber, | ||
string, | ||
string, | ||
BigNumber | ||
]; | ||
type RedeemEventArgs = [ | ||
string, | ||
string | ||
]; | ||
type RefundEventArgs = [ | ||
string | ||
]; | ||
type EventArgs<T extends EventType> = T extends EventType.OPEN ? OpenEventArgs : T extends EventType.REDEEM ? RedeemEventArgs : T extends EventType.REFUND ? RefundEventArgs : never; | ||
type OpenResult = ReadonlyArray<any> & OpenEventArgs & { | ||
id: string; | ||
token: string; | ||
amount: BigNumber; | ||
recipient: string; | ||
hash: string; | ||
timeout: BigNumber; | ||
}; | ||
type RedeemResult = ReadonlyArray<any> & RedeemEventArgs & { | ||
id: string; | ||
secret: string; | ||
}; | ||
type RefundResult = ReadonlyArray<any> & RefundEventArgs & { | ||
id: string; | ||
}; | ||
export interface Event<T extends EventType> extends EthersEvent { | ||
args: T extends EventType.OPEN ? OpenResult : T extends EventType.REDEEM ? RedeemResult : T extends EventType.REFUND ? RefundResult : never; | ||
} | ||
export type GenericEvent = Event<EventType>; | ||
export interface Web3Client { | ||
htlcContract: Contract; | ||
currentBlock: () => number | Promise<number>; | ||
startBlock: number; | ||
endBlock?: number; | ||
} | ||
export declare class Erc20AssetAdapter implements AssetAdapter<SwapAsset.USDC | SwapAsset.USDC_MATIC | SwapAsset.USDT> { | ||
client: Web3Client; | ||
private cancelCallback; | ||
private stopped; | ||
constructor(client: Web3Client); | ||
findLog<T extends EventType>(filter: EventFilter, test?: (...args: [...EventArgs<T>, Event<T>]) => boolean | Promise<boolean>): Promise<Event<T>>; | ||
awaitHtlcFunding(htlcId: string, value: number, data?: string, confirmations?: number, onPending?: (tx: GenericEvent) => any): Promise<Event<EventType.OPEN>>; | ||
fundHtlc(_serializedTx: string): Promise<Event<EventType.OPEN>>; | ||
awaitHtlcSettlement(htlcId: string): Promise<Event<EventType.REDEEM>>; | ||
awaitSwapSecret(htlcId: string): Promise<string>; | ||
settleHtlc(_serializedTx: string, _secret: string): Promise<Event<EventType.REDEEM>>; | ||
awaitSettlementConfirmation(htlcId: string): Promise<Event<EventType.REDEEM>>; | ||
stop(reason: Error): void; | ||
} | ||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
export var EventType; | ||
(function (EventType) { | ||
EventType["OPEN"] = "Open"; | ||
EventType["REDEEM"] = "Redeem"; | ||
EventType["REFUND"] = "Refund"; | ||
})(EventType || (EventType = {})); | ||
export class Erc20AssetAdapter { | ||
constructor(client) { | ||
this.client = client; | ||
this.cancelCallback = null; | ||
this.stopped = false; | ||
} | ||
async findLog(filter, test) { | ||
return new Promise(async (resolve, reject) => { | ||
const listener = async (...args) => { | ||
if (test && !(await test.apply(this, args))) | ||
return false; | ||
cleanup(); | ||
resolve(args[args.length - 1]); | ||
return true; | ||
}; | ||
this.client.htlcContract.on(filter, listener); | ||
const checkHistory = async () => { | ||
const history = await this.client.htlcContract.queryFilter(filter, this.client.startBlock, this.client.endBlock); | ||
for (const event of history) { | ||
if (!event.args) | ||
continue; | ||
if (await listener(...event.args, event)) | ||
break; | ||
} | ||
}; | ||
checkHistory(); | ||
const historyCheckInterval = window.setInterval(checkHistory, 60 * 1000); | ||
const cleanup = () => { | ||
this.client.htlcContract.off(filter, listener); | ||
window.clearInterval(historyCheckInterval); | ||
this.cancelCallback = null; | ||
}; | ||
this.cancelCallback = (reason) => { | ||
cleanup(); | ||
reject(reason); | ||
}; | ||
}); | ||
} | ||
async awaitHtlcFunding(htlcId, value, data, confirmations = 0, onPending) { | ||
const filter = this.client.htlcContract.filters.Open(htlcId); | ||
return this.findLog(filter, async (id, token, amount, recipient, hash, timeout, log) => { | ||
if (amount.toNumber() !== value) { | ||
console.warn(`Found ERC-20 HTLC, but amount does not match. Expected ${value}, found ${amount.toNumber()}`); | ||
return false; | ||
} | ||
if (confirmations > 0) { | ||
const logConfirmations = await this.client.currentBlock() - log.blockNumber + 1; | ||
if (logConfirmations < confirmations) { | ||
if (typeof onPending === 'function') | ||
onPending(log); | ||
return false; | ||
} | ||
} | ||
return true; | ||
}); | ||
} | ||
async fundHtlc(_serializedTx) { | ||
throw new Error('Method "fundHtlc" not available for ERC-20 HTLCs'); | ||
} | ||
async awaitHtlcSettlement(htlcId) { | ||
const filter = this.client.htlcContract.filters.Redeem(htlcId); | ||
return this.findLog(filter); | ||
} | ||
async awaitSwapSecret(htlcId) { | ||
const log = await this.awaitHtlcSettlement(htlcId); | ||
return log.args.secret.substring(2); | ||
} | ||
async settleHtlc(_serializedTx, _secret) { | ||
throw new Error('Method "settleHtlc" not available for ERC-20 HTLCs'); | ||
} | ||
async awaitSettlementConfirmation(htlcId) { | ||
return this.awaitHtlcSettlement(htlcId); | ||
} | ||
stop(reason) { | ||
if (this.cancelCallback) | ||
this.cancelCallback(reason); | ||
this.stopped = true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Htlc, HtlcStatus, SettlementTokens } from '@nimiq/oasis-api'; | ||
import type { AssetAdapter, FiatSwapAsset } from './IAssetAdapter'; | ||
export { Htlc as OasisHtlcDetails, SettlementTokens as OasisSettlementTokens }; | ||
export interface OasisClient { | ||
getHtlc(id: string): Promise<Htlc>; | ||
settleHtlc(id: string, secret: string, settlementJWS: string, tokens?: SettlementTokens): Promise<Htlc>; | ||
} | ||
export declare class FiatAssetAdapter implements AssetAdapter<FiatSwapAsset> { | ||
client: OasisClient; | ||
private cancelCallback; | ||
private stopped; | ||
constructor(client: OasisClient); | ||
private findTransaction; | ||
awaitHtlcFunding(id: string, value: number, data?: string, confirmations?: number, onUpdate?: (htlc: Htlc) => any): Promise<Htlc>; | ||
fundHtlc(): Promise<Htlc>; | ||
awaitHtlcSettlement(id: string): Promise<Htlc<HtlcStatus.SETTLED>>; | ||
awaitSwapSecret(id: string): Promise<string>; | ||
settleHtlc(settlementJWS: string, secret: string, hash: string, tokens?: SettlementTokens): Promise<Htlc>; | ||
awaitSettlementConfirmation(id: string, onUpdate?: (tx: Htlc) => any): Promise<Htlc>; | ||
stop(reason: Error): void; | ||
} |
Oops, something went wrong.