Skip to content

Commit

Permalink
feat: cooperative EVM refunds
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 committed Feb 6, 2024
1 parent c6a1f4c commit 87f952a
Show file tree
Hide file tree
Showing 16 changed files with 694 additions and 187 deletions.
2 changes: 1 addition & 1 deletion lib/Core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export const constructClaimDetails = (
swap.redeemScript!,
);
claimDetails.internalKey = createMusig(
claimDetails.keys,
claimDetails.keys!,
getHexBuffer(swap.refundPublicKey!),
).getAggregatedPublicKey();
break;
Expand Down
45 changes: 45 additions & 0 deletions lib/api/v2/routers/SwapRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,41 @@ class SwapRouter extends RouterBase {
*/
router.post('/submarine/refund', this.handleError(this.refundSubmarine));

/**
* @openapi
* /swap/submarine/{id}/refund:
* get:
* tags: [Submarine]
* description: Get an EIP-712 signature for a cooperative EVM refund
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: ID of the Swap
* responses:
* '200':
* description: EIP-712 signature
* content:
* application/json:
* schema:
* properties:
* signature:
* type: string
* description: EIP-712 signature with which a cooperative refund can be executed onchain
* '400':
* description: Error that caused signature request to fail
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponse'
*/
router.get(
'/submarine/:id/refund',
this.handleError(this.refundSubmarineEvm),
);

/**
* @openapi
* components:
Expand Down Expand Up @@ -979,6 +1014,16 @@ class SwapRouter extends RouterBase {
});
};

private refundSubmarineEvm = async (req: Request, res: Response) => {
const { id } = validateRequest(req.params, [
{ name: 'id', type: 'string' },
]);

successResponse(res, {
signature: await this.service.eipSigner.signSwapRefund(id),
});
};

private getSubmarineClaimDetails = async (req: Request, res: Response) => {
const { id } = validateRequest(req.params, [
{ name: 'id', type: 'string' },
Expand Down
6 changes: 4 additions & 2 deletions lib/cli/ethereum/commands/Claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ export const handler = async (argv: Arguments<any>): Promise<void> => {
argv.queryStartHeightDelta,
),
);
transaction = await erc20Swap.claim(
transaction = await erc20Swap[
'claim(bytes32,uint256,address,address,uint256)'
](
preimage,
erc20SwapValues.amount,
erc20SwapValues.tokenAddress,
Expand All @@ -60,7 +62,7 @@ export const handler = async (argv: Arguments<any>): Promise<void> => {
argv.queryStartHeightDelta,
),
);
transaction = await etherSwap.claim(
transaction = await etherSwap['claim(bytes32,uint256,address,uint256)'](
preimage,
etherSwapValues.amount,
etherSwapValues.refundAddress,
Expand Down
8 changes: 8 additions & 0 deletions lib/service/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import PaymentRequestUtils from './PaymentRequestUtils';
import TimeoutDeltaProvider, {
PairTimeoutBlocksDelta,
} from './TimeoutDeltaProvider';
import EipSigner from './cooperative/EipSigner';
import MusigSigner from './cooperative/MusigSigner';

type NetworkContracts = {
Expand Down Expand Up @@ -110,6 +111,8 @@ class Service {
public swapManager: SwapManager;
public eventHandler: EventHandler;
public elementsService: ElementsService;

public readonly eipSigner: EipSigner;
public readonly musigSigner: MusigSigner;
public readonly rateProvider: RateProvider;

Expand Down Expand Up @@ -187,6 +190,11 @@ class Service {
this.currencies,
this.walletManager,
);
this.eipSigner = new EipSigner(
this.logger,
this.currencies,
this.walletManager,
);
this.musigSigner = new MusigSigner(
this.logger,
this.currencies,
Expand Down
146 changes: 146 additions & 0 deletions lib/service/cooperative/EipSigner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import Logger from '../../Logger';
import {
getChainCurrency,
getHexBuffer,
getLightningCurrency,
splitPairId,
} from '../../Utils';
import { etherDecimals } from '../../consts/Consts';
import Swap from '../../db/models/Swap';
import SwapRepository from '../../db/repositories/SwapRepository';
import WalletManager, { Currency } from '../../wallet/WalletManager';
import EthereumManager from '../../wallet/ethereum/EthereumManager';
import ERC20WalletProvider from '../../wallet/providers/ERC20WalletProvider';
import Errors from '../Errors';
import MusigSigner from './MusigSigner';

class EipSigner {
constructor(
private readonly logger: Logger,
private readonly currencies: Map<string, Currency>,
private readonly walletManager: WalletManager,
) {}

public signSwapRefund = async (swapId: string) => {
const swap = await SwapRepository.getSwap({ id: swapId });
if (!swap) {
throw Errors.SWAP_NOT_FOUND(swapId);
}

const { base, quote } = splitPairId(swap.pair);

if (
!(await MusigSigner.isEligibleForRefund(
swap,
this.currencies.get(
getLightningCurrency(base, quote, swap.orderSide, false),
)!,
))
) {
this.logger.verbose(
`Not creating EIP-712 signature for refund of Swap ${swap.id}: it is not eligible`,
);
throw Errors.NOT_ELIGIBLE_FOR_COOPERATIVE_REFUND();
}

this.logger.debug(
`Creating EIP-712 signature for refund of Swap ${swap.id}`,
);

const chainSymbol = getChainCurrency(base, quote, swap.orderSide, false);
const manager = this.walletManager.ethereumManagers.find((man) =>
man.hasSymbol(chainSymbol),
);

if (manager === undefined) {
throw 'no signer for currency';
}

const { domain, types, value } = await this.getSigningData(
manager,
chainSymbol,
swap,
);
return manager.signer.signTypedData(domain, types, value);
};

private getSigningData = async (
manager: EthereumManager,
chainSymbol: string,
swap: Swap,
) => {
const isEtherSwap = manager.networkDetails.symbol === chainSymbol;
const contract = isEtherSwap ? manager.etherSwap : manager.erc20Swap;

const value: Record<string, any> = {
claimAddress: manager.address,
timeout: swap.timeoutBlockHeight,
preimageHash: getHexBuffer(swap.preimageHash),
amount: isEtherSwap
? BigInt(swap.onchainAmount!) * etherDecimals
: (
this.walletManager.wallets.get(chainSymbol)!
.walletProvider as ERC20WalletProvider
).formatTokenAmount(swap.onchainAmount!),
};

if (!isEtherSwap) {
value.tokenAddress = manager.tokenAddresses.get(chainSymbol);
}

return {
value,
domain: {
name: isEtherSwap ? 'EtherSwap' : 'ERC20Swap',
version: (await contract.version()).toString(),
chainId: manager.network.chainId,
verifyingContract: await contract.getAddress(),
},
types: {
Refund: isEtherSwap
? [
{
type: 'bytes32',
name: 'preimageHash',
},
{
type: 'uint256',
name: 'amount',
},
{
type: 'address',
name: 'claimAddress',
},
{
type: 'uint256',
name: 'timeout',
},
]
: [
{
type: 'bytes32',
name: 'preimageHash',
},
{
type: 'uint256',
name: 'amount',
},
{
type: 'address',
name: 'tokenAddress',
},
{
type: 'address',
name: 'claimAddress',
},
{
type: 'uint256',
name: 'timeout',
},
],
},
};
};
}

export default EipSigner;
Loading

0 comments on commit 87f952a

Please sign in to comment.