Skip to content
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

feat: add xrpl ticket feature #42

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0a35add
Add initial support for Ripple, RippleHandler
sosaucily Sep 25, 2024
b12c077
remove some noisy debug statements;
sosaucily Sep 30, 2024
f00241c
feat: add check functions to ripplehandler
Polybius93 Sep 30, 2024
8eed719
chore: merge branch 'support-xrpl' into feat/support-xrpl-check
Polybius93 Sep 30, 2024
09fe59b
feat: modify ripple handler to handle token minting
Polybius93 Oct 2, 2024
30014a7
feat: modify customer wallet seed
Polybius93 Oct 5, 2024
46216c7
Merge pull request #32 from DLC-link/feat/support-xrpl-check
sosaucily Oct 5, 2024
e67a4a5
Support multisig for XRPL txs
sosaucily Oct 7, 2024
8b71cb6
feat: abstract xrpl functions, modifiy ripple handler
Polybius93 Oct 10, 2024
4d6d192
feat: modify ripple functions
Polybius93 Oct 14, 2024
6d7af06
remove NFT burning, only mint more
sosaucily Oct 14, 2024
9810560
sort by nft_serial when getting all vaults
sosaucily Oct 14, 2024
112d065
feat: add ledger-xrp-handler
Polybius93 Oct 15, 2024
a7ff441
chore: revert "sort by nft_serial when getting all vaults"
Polybius93 Oct 15, 2024
cfbd637
chore: revert "remove NFT burning, only mint more"
Polybius93 Oct 15, 2024
f73dc87
feat: add gem wallet xrp handler
Polybius93 Oct 15, 2024
ec5a5c9
feat: modify ripplehandler to have websockerurl argument in constructor
Polybius93 Oct 15, 2024
0ba53c9
feat: add minSigners argument to ripplehandler
Polybius93 Oct 16, 2024
b75773e
chore: bump version
Polybius93 Oct 16, 2024
5f0b1ba
feat: add timestamp to xrpl vaults
Polybius93 Oct 17, 2024
a643120
feat: modify currency symbol for xrpl
Polybius93 Oct 17, 2024
cb9348c
feat: modify limit of get nfts
Polybius93 Oct 17, 2024
181b7bf
chore: merge branch 'fix/nft-limit' into feat/support-xrpl
Polybius93 Oct 17, 2024
d072392
chore: merge branch 'feat/add-xrpl-date' into feat/support-xrpl
Polybius93 Oct 17, 2024
3c40342
chore: bump package version
Polybius93 Oct 17, 2024
9730076
feat: add cash check by tx id function (#33)
Polybius93 Oct 18, 2024
857107e
feat: add pagination to getAllRippleVaults
Polybius93 Oct 21, 2024
98b2804
feat: add fee arguments to setup vault
Polybius93 Oct 22, 2024
d14d875
feat: add ticket creation and signing
Polybius93 Oct 23, 2024
e7e4fd9
chore: merge branch 'main' into feat/add-ticket
Polybius93 Nov 27, 2024
b76e109
feat: add back create ticket function to ripple handler
Polybius93 Nov 27, 2024
6dbe960
feat: modify ripple handler ticket creation
Polybius93 Nov 28, 2024
430b530
feat: add ticket fields to xrpl functions
Polybius93 Nov 29, 2024
22386a9
chore: update version to 2.4.19.
Polybius93 Nov 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "dlc-btc-lib",
"version": "2.4.18",
"version": "2.4.19",
"description": "This library provides a comprehensive set of interfaces and functions for minting dlcBTC tokens on supported blockchains.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
37 changes: 37 additions & 0 deletions src/functions/ripple/ripple.functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import {
Client,
LedgerEntry,
SubmittableTransaction,
TicketCreate,
Transaction,
TransactionMetadataBase,
TrustSet,
TxResponse,
Wallet,
convertHexToString,
convertStringToHex,
multisign,
} from 'xrpl';

import {
Expand Down Expand Up @@ -405,3 +407,38 @@ export async function getCheckByTXHash(
throw new RippleError(`Error getting Check by TX Hash: ${error}`);
}
}

export async function createTicket(
xrplClient: Client,
accountAddress: string,
ticketAmount: number,
signerQuorum?: number
): Promise<TicketCreate> {
const createTicketRequest: TicketCreate = {
TransactionType: 'TicketCreate',
Account: accountAddress,
TicketCount: ticketAmount,
};

const updatedCreateTicketRequest = await xrplClient.autofill(createTicketRequest, signerQuorum);
return updatedCreateTicketRequest;
}

export async function signTransaction(
xrplWallet: Wallet,
transaction: SubmittableTransaction
): Promise<SignResponse> {
try {
return xrplWallet.sign(transaction);
} catch (error) {
throw new RippleError(`Error signing Create Ticket: ${error}`);
}
}

export function multiSignTransaction(signedTransactions: string[]): string {
try {
return multisign(signedTransactions);
} catch (error) {
throw new RippleError(`Error multi-signing Transaction: ${error}`);
}
}
2 changes: 1 addition & 1 deletion src/models/ripple.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface AutoFillValues {
Fee: string;
}

export type SignatureType = 'cashCheck' | 'burnNFT' | 'mintNFT' | 'mintToken';
export type SignatureType = 'cashCheck' | 'burnNFT' | 'mintNFT' | 'mintToken' | 'createTicket';

export interface XRPLSignatures {
signatureType: SignatureType;
Expand Down
138 changes: 118 additions & 20 deletions src/network-handlers/ripple-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,26 @@ import xrpl, {
AccountObject,
AccountObjectsResponse,
CheckCash,
CreatedNode,
IssuedCurrencyAmount,
Payment,
Request,
SubmittableTransaction,
TicketCreate,
TransactionMetadata,
} from 'xrpl';
import { NFTokenMintMetadata } from 'xrpl/dist/npm/models/transactions/NFTokenMint.js';

import { XRPL_DLCBTC_CURRENCY_HEX } from '../constants/ripple.constants.js';
import {
checkRippleTransactionResult,
connectRippleClient,
decodeURI,
encodeURI,
getAllRippleVaults,
getCheckByTXHash,
getRippleVault,
multiSignTransaction,
} from '../functions/ripple/ripple.functions.js';
import { RippleError } from '../models/errors.js';
import { RawVault, SSFVaultUpdate, SSPVaultUpdate } from '../models/ethereum-models.js';
Expand Down Expand Up @@ -150,12 +155,102 @@ export class RippleHandler {
});
}

async submitCreateTicketTransaction(xrplSignatures: XRPLSignatures[]): Promise<string[]> {
return await this.withConnectionMgmt(async () => {
try {
const signedTransactionBlobs = xrplSignatures.find(
sig => sig.signatureType === 'createTicket'
)!.signatures;
const multisignedTransaction = multiSignTransaction(signedTransactionBlobs);

const submitCreateTicketTransactionResponse =
await this.client.submitAndWait(multisignedTransaction);

checkRippleTransactionResult(submitCreateTicketTransactionResponse);

const meta = submitCreateTicketTransactionResponse.result.meta;

if (!meta) {
throw new RippleError('Transaction Metadata not found');
}

if (typeof meta === 'string') {
throw new RippleError(`Could not read Transaction Result of: ${meta}`);
}

const createdNodes = (meta as TransactionMetadata<TicketCreate>).AffectedNodes.filter(
(node): node is CreatedNode => 'CreatedNode' in node
).map(node => node.CreatedNode);

return createdNodes.map(node => node.NewFields.TicketSequence) as string[];
} catch (error) {
throw new RippleError(`Could not submit Ticket Transaction: ${error}`);
}
});
}

async createTicket(
ticketCount: number,
autoFillValues?: AutoFillValues
): Promise<MultisignatureTransactionResponse[]> {
return await this.withConnectionMgmt(async () => {
try {
const createTicketRequest: TicketCreate = {
TransactionType: 'TicketCreate',
Account: this.issuerAddress,
TicketCount: ticketCount,
};

const preparedCreateTicketTransaction = await this.client.autofill(
createTicketRequest,
this.minSigners
);
if (autoFillValues) {
preparedCreateTicketTransaction.Fee = autoFillValues.Fee;
preparedCreateTicketTransaction.LastLedgerSequence = autoFillValues.LastLedgerSequence;
preparedCreateTicketTransaction.Sequence = autoFillValues.Sequence;
} else {
// set the LastLedgerSequence to be rounded up to the nearest 10000.
// this is to ensure that the transaction is valid for a while, and that the different attestors all use a matching LLS value to have matching sigs
// The request has a timeout, so this shouldn't end up being a hanging request
// Using the ticket system would likely be a better way:
// https://xrpl.org/docs/concepts/accounts/tickets
preparedCreateTicketTransaction.LastLedgerSequence =
Math.ceil(preparedCreateTicketTransaction.LastLedgerSequence! / 10000 + 1) * 10000;
}

console.log('preparedCreateTicketTransaction ', preparedCreateTicketTransaction);

const createTicketTransactionSignature = this.wallet.sign(
preparedCreateTicketTransaction,
true
).tx_blob;
console.log('createTicketTransactionSignature: ', createTicketTransactionSignature);

return [
{
tx_blob: createTicketTransactionSignature,
autoFillValues: {
signatureType: 'createTicket',
LastLedgerSequence: preparedCreateTicketTransaction.LastLedgerSequence!,
Sequence: preparedCreateTicketTransaction.Sequence!,
Fee: preparedCreateTicketTransaction.Fee!,
},
},
];
} catch (error) {
throw new RippleError(`Could not create Ticket: ${error}`);
}
});
}

async setupVault(
uuid: string,
userAddress: string,
timeStamp: number,
btcMintFeeBasisPoints: number,
btcRedeemFeeBasisPoints: number,
ticket: string,
autoFillValues?: AutoFillValues[]
): Promise<MultisignatureTransactionResponse[]> {
return await this.withConnectionMgmt(async () => {
Expand All @@ -170,7 +265,7 @@ export class RippleHandler {
return [
await this.mintNFT(
newVault,
undefined,
ticket,
autoFillValues?.find(sig => sig.signatureType === 'mintNFT')
),
];
Expand All @@ -183,6 +278,7 @@ export class RippleHandler {
async withdraw(
uuid: string,
withdrawAmount: bigint,
tickets: string[],
autoFillValues: AutoFillValues[]
): Promise<MultisignatureTransactionResponse[]> {
return await this.withConnectionMgmt(async () => {
Expand All @@ -194,14 +290,14 @@ export class RippleHandler {
const thisVault = await this.getRawVault(nftUUID);
const burnSig = await this.burnNFT(
nftUUID,
1,
tickets[0],
autoFillValues.find(sig => sig.signatureType === 'burnNFT')
);

thisVault.valueMinted = thisVault.valueMinted.sub(BigNumber.from(withdrawAmount));
const mintSig = await this.mintNFT(
thisVault,
2,
tickets[1],
autoFillValues.find(sig => sig.signatureType === 'mintNFT')
);
return [burnSig, mintSig];
Expand Down Expand Up @@ -393,7 +489,7 @@ export class RippleHandler {

async burnNFT(
nftUUID: string,
incrementBy: number = 0,
ticket: string,
autoFillValues?: AutoFillValues
): Promise<MultisignatureTransactionResponse> {
return await this.withConnectionMgmt(async () => {
Expand All @@ -402,6 +498,8 @@ export class RippleHandler {
const nftTokenId = await this.getNFTokenIdForVault(nftUUID);
const burnTransactionJson: SubmittableTransaction = {
TransactionType: 'NFTokenBurn',
TicketSequence: new Decimal(ticket).toNumber(),
Sequence: 0,
Account: this.issuerAddress,
NFTokenID: nftTokenId,
};
Expand All @@ -420,10 +518,6 @@ export class RippleHandler {
// https://xrpl.org/docs/concepts/accounts/tickets
preparedBurnTx.LastLedgerSequence =
Math.ceil(preparedBurnTx.LastLedgerSequence! / 10000 + 1) * 10000;

if (incrementBy > 0) {
preparedBurnTx.Sequence = preparedBurnTx.Sequence! + incrementBy;
}
}

console.log('preparedBurnTx ', preparedBurnTx);
Expand All @@ -448,7 +542,7 @@ export class RippleHandler {

async mintNFT(
vault: RawVault,
incrementBy: number = 0,
ticket: string,
autoFillValues?: AutoFillValues
): Promise<MultisignatureTransactionResponse> {
return await this.withConnectionMgmt(async () => {
Expand All @@ -460,6 +554,8 @@ export class RippleHandler {
console.log('newURI: ', newURI);
const mintTransactionJson: SubmittableTransaction = {
TransactionType: 'NFTokenMint',
TicketSequence: new Decimal(ticket).toNumber(),
Sequence: 0,
Account: this.issuerAddress,
URI: newURI,
NFTokenTaxon: 0,
Expand All @@ -479,9 +575,6 @@ export class RippleHandler {
// https://xrpl.org/docs/concepts/accounts/tickets
preparedMintTx.LastLedgerSequence =
Math.ceil(preparedMintTx.LastLedgerSequence! / 10000 + 1) * 10000;
if (incrementBy > 0) {
preparedMintTx.Sequence = preparedMintTx.Sequence! + incrementBy;
}
}

console.log('preparedMintTx ', preparedMintTx);
Expand All @@ -505,6 +598,7 @@ export class RippleHandler {
async getSigUpdateVaultForSSP(
uuid: string,
updates: SSPVaultUpdate,
ticket: string,
autoFillValues?: AutoFillValues
): Promise<MultisignatureTransactionResponse> {
return await this.withConnectionMgmt(async () => {
Expand All @@ -520,7 +614,7 @@ export class RippleHandler {
taprootPubKey: updates.taprootPubKey,
};
console.log(`the updated vault: `, updatedVault);
return await this.mintNFT(updatedVault, 1, autoFillValues);
return await this.mintNFT(updatedVault, ticket, autoFillValues);
} catch (error) {
throw new RippleError(`Could not update Vault: ${error}`);
}
Expand All @@ -530,7 +624,7 @@ export class RippleHandler {
async getSigUpdateVaultForSSF(
uuid: string,
updates: SSFVaultUpdate,
updateSequenceBy: number,
ticket: string,
autoFillValues?: AutoFillValues
): Promise<MultisignatureTransactionResponse> {
return await this.withConnectionMgmt(async () => {
Expand All @@ -545,7 +639,7 @@ export class RippleHandler {
valueMinted: BigNumber.from(updates.valueMinted),
valueLocked: BigNumber.from(updates.valueLocked),
};
return await this.mintNFT(updatedVault, updateSequenceBy, autoFillValues);
return await this.mintNFT(updatedVault, ticket, autoFillValues);
} catch (error) {
throw new RippleError(`Could not update Vault: ${error}`);
}
Expand Down Expand Up @@ -575,6 +669,7 @@ export class RippleHandler {

async getCashCheckAndWithdrawSignatures(
txHash: string,
tickets: string[],
autoFillValues?: AutoFillValues[]
): Promise<MultisignatureTransactionResponse[]> {
return await this.withConnectionMgmt(async () => {
Expand All @@ -599,12 +694,14 @@ export class RippleHandler {
const checkCashSignatures = await this.cashCheck(
check.index,
checkSendMax.value,
tickets[0],
autoFillValues?.find(sig => sig.signatureType === 'cashCheck')
);

const mintAndBurnSignatures = await this.withdraw(
vault.uuid,
BigInt(shiftValue(Number(checkSendMax.value))),
tickets.slice(1),
autoFillValues ?? []
);
return [checkCashSignatures, ...mintAndBurnSignatures];
Expand All @@ -617,6 +714,7 @@ export class RippleHandler {
async cashCheck(
checkID: string,
dlcBTCAmount: string,
ticket: string,
autoFillValues?: AutoFillValues
): Promise<MultisignatureTransactionResponse> {
return await this.withConnectionMgmt(async () => {
Expand All @@ -627,6 +725,8 @@ export class RippleHandler {
TransactionType: 'CheckCash',
Account: this.issuerAddress,
CheckID: checkID,
TicketSequence: new Decimal(ticket).toNumber(),
Sequence: 0,
Amount: {
currency: XRPL_DLCBTC_CURRENCY_HEX,
value: dlcBTCAmount,
Expand Down Expand Up @@ -679,7 +779,7 @@ export class RippleHandler {
updatedValueMinted: number,
destinationAddress: string,
valueMinted: number,
incrementBy: number = 0,
ticket: string,
autoFillValues?: AutoFillValues
): Promise<MultisignatureTransactionResponse | undefined> {
return await this.withConnectionMgmt(async () => {
Expand All @@ -697,6 +797,8 @@ export class RippleHandler {
const sendTokenTransactionJSON: Payment = {
TransactionType: 'Payment',
Account: this.issuerAddress,
TicketSequence: new Decimal(ticket).toNumber(),
Sequence: 0,
Destination: destinationAddress,
DestinationTag: 1,
Amount: {
Expand All @@ -723,10 +825,6 @@ export class RippleHandler {
// https://xrpl.org/docs/concepts/accounts/tickets
preparedSendTokenTx.LastLedgerSequence =
Math.ceil(preparedSendTokenTx.LastLedgerSequence! / 10000 + 1) * 10000;

if (incrementBy > 0) {
preparedSendTokenTx.Sequence = preparedSendTokenTx.Sequence! + incrementBy;
}
}

console.log('Issuer is about to sign the following mintTokens tx: ', preparedSendTokenTx);
Expand Down
Loading