diff --git a/client/PublicRequestTypes.ts b/client/PublicRequestTypes.ts index 8e885f7f..872807b8 100644 --- a/client/PublicRequestTypes.ts +++ b/client/PublicRequestTypes.ts @@ -288,6 +288,13 @@ export interface EuroHtlcCreationInstructions { bankLabel?: string; } +export interface SinpeMovilHtlcCreationInstructions { + type: 'CRC'; + value: number; // CRC cents + fee: number; // CRC cents + senderLabel?: string; +} + export interface NimiqHtlcSettlementInstructions { type: 'NIM'; recipient: string; // My address, must be redeem address of HTLC @@ -333,6 +340,19 @@ export interface EuroHtlcSettlementInstructions { }; } +export interface SinpeMovilHtlcSettlementInstructions { + type: 'CRC'; + value: number; // CRC cents + fee: number; // CRC cents + recipientLabel?: string; + settlement: { + type: 'sinpemovil', + phoneNumber: string, + } | { + type: 'mock', + }; +} + export interface NimiqHtlcRefundInstructions { type: 'NIM'; sender: string; // HTLC address @@ -368,13 +388,15 @@ export type HtlcCreationInstructions = NimiqHtlcCreationInstructions | BitcoinHtlcCreationInstructions | PolygonHtlcCreationInstructions - | EuroHtlcCreationInstructions; + | EuroHtlcCreationInstructions + | SinpeMovilHtlcCreationInstructions; export type HtlcSettlementInstructions = NimiqHtlcSettlementInstructions | BitcoinHtlcSettlementInstructions | PolygonHtlcSettlementInstructions - | EuroHtlcSettlementInstructions; + | EuroHtlcSettlementInstructions + | SinpeMovilHtlcSettlementInstructions; export type HtlcRefundInstructions = NimiqHtlcRefundInstructions @@ -428,6 +450,7 @@ export interface SetupSwapResult { btc?: SignedBtcTransaction; usdc?: SignedPolygonTransaction; eur?: string; // When funding EUR: empty string, when redeeming EUR: JWS of the settlement instructions + crc?: string; // When funding CRC: empty string, when redeeming CRC: JWS of the settlement instructions refundTx?: string; } diff --git a/client/package.json b/client/package.json index c200843d..29dfd690 100644 --- a/client/package.json +++ b/client/package.json @@ -10,7 +10,7 @@ "types": "types/index.d.ts", "dependencies": { "@nimiq/core-web": "^1.6.1", - "@nimiq/fastspot-api": "^1.8.0", + "@nimiq/fastspot-api": "https://github.com/nimiq/fastspot-api#3a7c4b68529d7ec9ba8955a399412eaae946c528", "@nimiq/rpc": "^0.4.0", "@nimiq/utils": "^0.5.0", "@opengsn/common": "^2.2.5", diff --git a/client/yarn.lock b/client/yarn.lock index 869b1b32..0bc2e500 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -232,10 +232,9 @@ resolved "https://registry.yarnpkg.com/@nimiq/core-web/-/core-web-1.6.1.tgz#97cb5b43b257c7f6f6808ef603e9bf686377241f" integrity sha512-WYw2brIxUXa/SQ0JRp0RXWQKzBFhROXrEjF9Eh+tRlC+NrI2ObwRQkwJCbP2qmPtYldIimfyECmsDVHFoyLXjQ== -"@nimiq/fastspot-api@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@nimiq/fastspot-api/-/fastspot-api-1.8.0.tgz#705a9e79e425c3e6536d8994fd0b39d88af1b268" - integrity sha512-qNkibJnxS8ndOn4tuy1m3lSNKybBYApo+wy1ajTKcQ0lHo3VfLY0sAJ+WRE7diVWCa7iumu6wsFVudyc3k8/NQ== +"@nimiq/fastspot-api@https://github.com/nimiq/fastspot-api#3a7c4b68529d7ec9ba8955a399412eaae946c528": + version "1.9.0" + resolved "https://github.com/nimiq/fastspot-api#3a7c4b68529d7ec9ba8955a399412eaae946c528" "@nimiq/rpc@^0.4.0": version "0.4.0" diff --git a/package.json b/package.json index bb74c2ec..5f54df81 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,12 @@ "dependencies": { "@nimiq/browser-warning": "^1.1.1", "@nimiq/electrum-client": "https://github.com/nimiq/electrum-client#build", - "@nimiq/fastspot-api": "^1.8.0", + "@nimiq/fastspot-api": "https://github.com/nimiq/fastspot-api#3a7c4b68529d7ec9ba8955a399412eaae946c528", "@nimiq/iqons": "^1.5.2", - "@nimiq/keyguard-client": "^1.6.0", + "@nimiq/keyguard-client": "https://gitpkg.vercel.app/nimiq/keyguard?scripts.postinstall=cd%20client%20%26%26%20.%2Fbuild-gitpkg.sh&cb20092889dd9fd815366531e7400b8cd60c51af", "@nimiq/ledger-api": "^2.3.0", "@nimiq/network-client": "^0.6.2", - "@nimiq/oasis-api": "^1.1.1", + "@nimiq/oasis-api": "github:nimiq/oasis-api-js#fbd74179a5b9590f8e6dfe9b08ca47122db2ce1e", "@nimiq/rpc": "^0.4.1", "@nimiq/style": "^0.8.2", "@nimiq/utils": "^0.11.1", diff --git a/src/lib/RequestParser.ts b/src/lib/RequestParser.ts index c5ea0577..5f0ee8ac 100644 --- a/src/lib/RequestParser.ts +++ b/src/lib/RequestParser.ts @@ -527,11 +527,11 @@ export class RequestParser { // Validate and parse only what we use in the Hub - if (!['NIM', 'BTC', 'USDC_MATIC', 'EUR'].includes(setupSwapRequest.fund.type)) { + if (!['NIM', 'BTC', 'USDC_MATIC', 'EUR', 'CRC'].includes(setupSwapRequest.fund.type)) { throw new Error('Funding type is not supported'); } - if (!['NIM', 'BTC', 'USDC_MATIC', 'EUR'].includes(setupSwapRequest.redeem.type)) { + if (!['NIM', 'BTC', 'USDC_MATIC', 'EUR', 'CRC'].includes(setupSwapRequest.redeem.type)) { throw new Error('Redeeming type is not supported'); } @@ -631,7 +631,10 @@ export class RequestParser { } : setupSwapRequest.fund.type === 'USDC_MATIC' ? { ...setupSwapRequest.fund, type: SwapAsset[setupSwapRequest.fund.type], - } : { // EUR + } : setupSwapRequest.fund.type === 'EUR' ? { + ...setupSwapRequest.fund, + type: SwapAsset[setupSwapRequest.fund.type], + } : { // CRC ...setupSwapRequest.fund, type: SwapAsset[setupSwapRequest.fund.type], }, @@ -649,7 +652,10 @@ export class RequestParser { } : setupSwapRequest.redeem.type === 'USDC_MATIC' ? { ...setupSwapRequest.redeem, type: SwapAsset[setupSwapRequest.redeem.type], - } : { // EUR + } : setupSwapRequest.redeem.type === 'EUR' ? { + ...setupSwapRequest.redeem, + type: SwapAsset[setupSwapRequest.redeem.type], + } : { // CRC ...setupSwapRequest.redeem, type: SwapAsset[setupSwapRequest.redeem.type], }, diff --git a/src/lib/RequestTypes.ts b/src/lib/RequestTypes.ts index 99b6124d..5b5c2e9c 100644 --- a/src/lib/RequestTypes.ts +++ b/src/lib/RequestTypes.ts @@ -209,6 +209,11 @@ export interface ParsedSetupSwapRequest extends ParsedSimpleRequest { value: number, // Eurocents fee: number, // Eurocents bankLabel?: string, + } | { + type: SwapAsset.CRC, + value: number, // CRC cents + fee: number, // CRC cents + senderLabel?: string, }; redeem: { @@ -251,6 +256,17 @@ export interface ParsedSetupSwapRequest extends ParsedSimpleRequest { } | { type: 'mock', }; + } | { + type: SwapAsset.CRC, + value: number; // CRC cents + fee: number; // CRC cents + recipientLabel?: string; + settlement: { + type: 'sinpemovil', + phoneNumber: string, + } | { + type: 'mock', + }; }; // Data needed for display diff --git a/src/views/SetupSwap.vue b/src/views/SetupSwap.vue index ba698a40..9ab2de0b 100644 --- a/src/views/SetupSwap.vue +++ b/src/views/SetupSwap.vue @@ -208,6 +208,15 @@ export default class SetupSwap extends BitcoinSyncBaseView { }; } + if (this.request.fund.type === SwapAsset.CRC) { + fundingInfo = { + type: SwapAsset.CRC, + amount: this.request.fund.value, + fee: this.request.fund.fee, + senderLabel: this.request.fund.senderLabel, + }; + } + if (this.request.redeem.type === SwapAsset.NIM) { const signer = this._account.findSignerForAddress(this.request.redeem.recipient); if (!signer) { @@ -282,6 +291,17 @@ export default class SetupSwap extends BitcoinSyncBaseView { }; } + if (this.request.redeem.type === SwapAsset.CRC) { + redeemingInfo = { + type: SwapAsset.CRC, + keyPath: DEFAULT_KEY_PATH, + settlement: this.request.redeem.settlement, + amount: this.request.redeem.value, + fee: this.request.redeem.fee, + recipientLabel: this.request.redeem.recipientLabel, + }; + } + if (!fundingInfo || !redeemingInfo) { throw new Error('Funding or redeeming info missing.'); } diff --git a/src/views/SetupSwapSuccess.vue b/src/views/SetupSwapSuccess.vue index a75b0b1a..200b3beb 100644 --- a/src/views/SetupSwapSuccess.vue +++ b/src/views/SetupSwapSuccess.vue @@ -16,7 +16,7 @@ import { Contract, UsdcHtlcDetails, } from '@nimiq/fastspot-api'; -import { init as initOasisApi, exchangeAuthorizationToken } from '@nimiq/oasis-api'; +import { exchangeAuthorizationToken } from '@nimiq/oasis-api'; import StatusScreen from '../components/StatusScreen.vue'; import GlobalClose from '../components/GlobalClose.vue'; import Network from '../components/Network.vue'; @@ -101,6 +101,7 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { case SwapAsset.USDC_MATIC: redeemAddress = this.request.redeem.request.from; break; + case SwapAsset.CRC: case SwapAsset.EUR: // Assemble recipient object redeemAddress = { @@ -118,16 +119,20 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { let confirmedSwap: Swap; try { const uid = this.request.kyc ? this.request.kyc.userId : await walletInfo.getUid(); + console.log('UID:', uid); const s3GrantToken = this.request.kyc ? this.request.kyc.s3GrantToken : undefined; + console.log('S3 grant token:', s3GrantToken); let oasisClearingAuthorizationToken: string | undefined; if (this.request.kyc && this.request.kyc.oasisGrantToken && this.request.fund.type === SwapAsset.EUR) { - initOasisApi(Config.oasis.apiEndpoint); - oasisClearingAuthorizationToken = await exchangeAuthorizationToken(this.request.kyc.oasisGrantToken); + oasisClearingAuthorizationToken = await exchangeAuthorizationToken( + Config.oasis.apiEndpoint, + this.request.kyc.oasisGrantToken, + ); } confirmedSwap = await confirmSwap({ id: this.request.swapId, - } as PreSwap, this.request.redeem.type === SwapAsset.EUR ? { + } as PreSwap, this.request.redeem.type === SwapAsset.EUR || this.request.redeem.type === SwapAsset.CRC ? { asset: this.request.redeem.type, ...(redeemAddress as { kty: string, crv: string, x: string }), } : { @@ -276,6 +281,18 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { // TODO: Validate correct recipient public key } + if (confirmedSwap.from.asset === SwapAsset.CRC || confirmedSwap.to.asset === SwapAsset.CRC) { + // TODO: Fetch contract from OASIS API and compare instead of trusting Fastspot + + if (hashRoot && confirmedSwap.hash !== hashRoot) { + this.$rpc.reject(new Error('HTLC hash roots do not match')); + return; + } + hashRoot = confirmedSwap.hash; + + // TODO: Validate correct recipient public key + } + if (!hashRoot) { this.$rpc.reject(new Error('UNEXPECTED: Could not extract swap hash from contracts')); return; @@ -330,6 +347,18 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { }; } + if (this.request.fund.type === SwapAsset.CRC) { + const crcContract = confirmedSwap.contracts[SwapAsset.CRC] as Contract; + const crcHtlcData = crcContract.htlc; + + fundingHtlcInfo = { + type: SwapAsset.CRC, + hash: hashRoot, + timeout: crcContract.timeout, + htlcId: crcHtlcData.address, + }; + } + if (this.request.redeem.type === SwapAsset.NIM) { const nimHtlcData = confirmedSwap.contracts[SwapAsset.NIM]!.htlc as NimHtlcDetails; @@ -433,6 +462,18 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { }; } + if (this.request.redeem.type === SwapAsset.CRC) { + const crcContract = confirmedSwap.contracts[SwapAsset.CRC] as Contract; + const crcHtlcData = crcContract.htlc; + + redeemingHtlcInfo = { + type: SwapAsset.CRC, + hash: hashRoot, + timeout: crcContract.timeout, + htlcId: crcHtlcData.address, + }; + } + if (this._isDestroyed) return; if (!fundingHtlcInfo || !redeemingHtlcInfo) { @@ -448,6 +489,7 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { let polygonTransaction: SignedPolygonTransaction | undefined; let refundTransaction: string | undefined; let euroSettlement: string | undefined; + let crcSettlement: string | undefined; try { const signingResult = await this._signSwapTransactions({ fund: fundingHtlcInfo, @@ -459,6 +501,7 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { nimProxy: nimiqProxyTransaction, btc: bitcoinTransaction, eur: euroSettlement, + crc: crcSettlement, usdc: polygonTransaction, refundTx: refundTransaction, } = signingResult); @@ -505,6 +548,7 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { btc: bitcoinTransaction, usdc: polygonTransaction, eur: euroSettlement, + crc: crcSettlement, refundTx: refundTransaction, }; @@ -522,10 +566,10 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { protected _getOasisRecipientPublicKey() { // note that this method gets overwritten for SetupSwapLedger - if (!this.keyguardResult || !this.keyguardResult.eurPubKey) { + if (!this.keyguardResult || !this.keyguardResult.fiatPubKey) { throw new Error('Cannot find OASIS recipient public key'); } - return Nimiq.BufferUtils.toBase64Url(Nimiq.BufferUtils.fromHex(this.keyguardResult.eurPubKey)) + return Nimiq.BufferUtils.toBase64Url(Nimiq.BufferUtils.fromHex(this.keyguardResult.fiatPubKey)) .replace(/\.*$/, ''); // OASIS cannot handle trailing filler dots } @@ -535,6 +579,7 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { btc?: SignedBtcTransaction, usdc?: SignedPolygonTransaction, eur?: string, + crc?: string, refundTx?: string, } | null> { // Note that this method gets overwritten for SetupSwapLedger @@ -550,6 +595,7 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { btc: bitcoinTransaction, usdc: polygonTransaction, eur: euroSettlement, + crc: crcSettlement, refundTx, } = await client.signSwapTransactions(keyguardRequest); @@ -584,6 +630,7 @@ export default class SetupSwapSuccess extends BitcoinSyncBaseView { } : undefined, usdc: polygonTransaction, eur: euroSettlement, + crc: crcSettlement, refundTx, }; } diff --git a/yarn.lock b/yarn.lock index 9fd5e4b7..61e52119 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1978,11 +1978,6 @@ resolved "https://registry.yarnpkg.com/@nimiq/core-web/-/core-web-1.5.3.tgz#f0a4de59394f210f2c2d9cda8ee35c716847d40e" integrity sha512-W66SS9n3ygYgD52r1GJr1WtYYOkcZsqdtMmDCEwDvkrmeARnHs2sAvj77Wt4PQG8JA7GwK5svIJr6rGccCaekw== -"@nimiq/core-web@1.5.8": - version "1.5.8" - resolved "https://registry.yarnpkg.com/@nimiq/core-web/-/core-web-1.5.8.tgz#da8abef84c6d293cbb9ca495a77f08daa08886b9" - integrity sha512-MNpFbGZetz2eZcHtJVa5tpmLjPf65mTcJBRQkOHS8kWE+f+Z++hdnwWUBQCEAph4oC6bsE5JSuU7VpwcogN7+w== - "@nimiq/core-web@^1.6.0", "@nimiq/core-web@^1.6.1": version "1.6.1" resolved "https://registry.yarnpkg.com/@nimiq/core-web/-/core-web-1.6.1.tgz#97cb5b43b257c7f6f6808ef603e9bf686377241f" @@ -2012,10 +2007,9 @@ dependencies: bitcoinjs-lib "^5.1.10" -"@nimiq/fastspot-api@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@nimiq/fastspot-api/-/fastspot-api-1.8.0.tgz#705a9e79e425c3e6536d8994fd0b39d88af1b268" - integrity sha512-qNkibJnxS8ndOn4tuy1m3lSNKybBYApo+wy1ajTKcQ0lHo3VfLY0sAJ+WRE7diVWCa7iumu6wsFVudyc3k8/NQ== +"@nimiq/fastspot-api@https://github.com/nimiq/fastspot-api#3a7c4b68529d7ec9ba8955a399412eaae946c528": + version "1.9.0" + resolved "https://github.com/nimiq/fastspot-api#3a7c4b68529d7ec9ba8955a399412eaae946c528" "@nimiq/iqons@^1.5.2", "@nimiq/iqons@^1.6.0": version "1.6.0" @@ -2033,14 +2027,9 @@ btoa "^1.1.2" node-lmdb "^0.9.6" -"@nimiq/keyguard-client@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@nimiq/keyguard-client/-/keyguard-client-1.6.0.tgz#e2cb22c1af5ae68dac74e5ead9e69aeaba2ffc8b" - integrity sha512-DDi+PycBtiTJO5Jwk3mHZzBP2FHARNCIR+9C6+uhw6kRlT/pcDS+vE6GeTGOwZsRGOtowLG3d9n43xWoPOOcGQ== - dependencies: - "@nimiq/core-web" "1.5.8" - "@nimiq/rpc" "^0.3.0" - "@opengsn/common" "^2.2.5" +"@nimiq/keyguard-client@https://gitpkg.vercel.app/nimiq/keyguard?scripts.postinstall=cd%20client%20%26%26%20.%2Fbuild-gitpkg.sh&cb20092889dd9fd815366531e7400b8cd60c51af": + version "1.0.0" + resolved "https://gitpkg.vercel.app/nimiq/keyguard?scripts.postinstall=cd%20client%20%26%26%20.%2Fbuild-gitpkg.sh&cb20092889dd9fd815366531e7400b8cd60c51af#2842141e0ac29c7eafee2748616924f5052c8f59" "@nimiq/ledger-api@^2.3.0": version "2.3.0" @@ -2069,10 +2058,9 @@ "@nimiq/core-web" "1.5.3" "@nimiq/rpc-events" "^0.0.8" -"@nimiq/oasis-api@^1.1.1": +"@nimiq/oasis-api@github:nimiq/oasis-api-js#fbd74179a5b9590f8e6dfe9b08ca47122db2ce1e": version "1.1.1" - resolved "https://registry.yarnpkg.com/@nimiq/oasis-api/-/oasis-api-1.1.1.tgz#d1aa3d0f9afabf9116205809a0fc72d2e392bafa" - integrity sha512-jXNO7nvE0mzAyDCrucByv5AcyDWZjyqb9810YctysxqUtOxNG27hPkhomL+WW6nrnTVgvriJo3yaTFPTfLphKw== + resolved "https://codeload.github.com/nimiq/oasis-api-js/tar.gz/fbd74179a5b9590f8e6dfe9b08ca47122db2ce1e" "@nimiq/rpc-events@^0.0.8": version "0.0.8" @@ -2086,11 +2074,6 @@ resolved "https://registry.yarnpkg.com/@nimiq/rpc/-/rpc-0.1.5.tgz#53919b0a3a9abcdfebee0e865f4c663f72a8b8c2" integrity sha512-oTRThXzpbQOY8jz8h+2KXucWzW40nVfYBWROKXKBrSozhTG0nR+rzCbEm4ZyTC26b4RnmB6y4nYabUXi7gNWcA== -"@nimiq/rpc@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@nimiq/rpc/-/rpc-0.3.0.tgz#654c05acccc193b7d79fb09b2faf2114945ff872" - integrity sha512-je7fv+wP4nLEgTcZwu3FaGre22qkZ9AYGbStglVaJAxOH+3CvDnnOIa9IjGFaCEhtRQKRaQEvFqa5vN4IVnH+Q== - "@nimiq/rpc@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@nimiq/rpc/-/rpc-0.4.1.tgz#d5df1e426793afcdd8c407a2968442bbee874dbd"