From 80cf68e71a14ecd37e902c7a901eb0734ffd8c0a Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Tue, 1 Oct 2024 16:14:58 +0300 Subject: [PATCH 01/51] init Iden3PaymentRailsRequestV1 --- .eslintrc.js | 2 +- src/iden3comm/handlers/payment.ts | 111 ++++++++++++++++++++---- src/iden3comm/types/protocol/payment.ts | 62 ++++++++++--- src/verifiable/constants.ts | 6 +- tests/handlers/payment.test.ts | 56 +++++++----- 5 files changed, 182 insertions(+), 55 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 12e32cdb..0c22a8c9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,7 +10,7 @@ module.exports = { ...spellcheckerRule, cspell: { ...cspellConfig, - ignoreWords: ['unmarshal', 'JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw', 'gdwj', 'fwor'] + ignoreWords: ['unmarshal', 'JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw', 'gdwj', 'fwor', 'multichain'] } } ] diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 1d980e7e..e43f3d8d 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -8,9 +8,12 @@ import { proving } from '@iden3/js-jwz'; import { byteEncoder } from '../../utils'; import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler'; import { - PaymentInfo, + Iden3PaymentCryptoV1, + Iden3PaymentRailsRequestV1, + Iden3PaymentRailsResponseV1, + Iden3PaymentRequestCryptoV1, PaymentMessage, - PaymentRequestDataInfo, + PaymentMessageBody, PaymentRequestInfo, PaymentRequestMessage } from '../types/protocol/payment'; @@ -52,10 +55,14 @@ export function createPaymentRequest( * createPayment is a function to create protocol payment message * @param {DID} sender - sender did * @param {DID} receiver - receiver did - * @param {PaymentInfo[]} payments - payments + * @param {PaymentMessageBody} body - payments * @returns `PaymentMessage` */ -export function createPayment(sender: DID, receiver: DID, payments: PaymentInfo[]): PaymentMessage { +export function createPayment( + sender: DID, + receiver: DID, + body: PaymentMessageBody +): PaymentMessage { const uuidv4 = uuid.v4(); const request: PaymentMessage = { id: uuidv4, @@ -64,9 +71,7 @@ export function createPayment(sender: DID, receiver: DID, payments: PaymentInfo[ to: receiver.string(), typ: MediaType.PlainMessage, type: PROTOCOL_MESSAGE_TYPE.PAYMENT_MESSAGE_TYPE, - body: { - payments - } + body }; return request; } @@ -110,13 +115,19 @@ export interface IPaymentHandler { /** @beta PaymentRequestMessageHandlerOptions represents payment-request handler options */ export type PaymentRequestMessageHandlerOptions = { - paymentHandler: (data: PaymentRequestDataInfo) => Promise; + paymentHandler: ( + data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 + ) => Promise; + multichainSelectedChainId?: string; }; /** @beta PaymentHandlerOptions represents payment handler options */ export type PaymentHandlerOptions = { paymentRequest: PaymentRequestMessage; - paymentValidationHandler: (txId: string, data: PaymentRequestDataInfo) => Promise; + paymentValidationHandler: ( + txId: string, + data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 + ) => Promise; }; /** @beta PaymentHandlerParams represents payment handler params */ @@ -202,13 +213,54 @@ export class PaymentHandler const senderDID = DID.parse(paymentRequest.to); const receiverDID = DID.parse(paymentRequest.from); - const payments: PaymentInfo[] = []; + const payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsResponseV1)[] = []; for (let i = 0; i < paymentRequest.body.payments.length; i++) { const paymentReq = paymentRequest.body.payments[i]; if (paymentReq.type !== PaymentRequestType.PaymentRequest) { throw new Error(`failed request. not supported '${paymentReq.type}' payment type `); } + // if multichain request + if (Array.isArray(paymentReq.data)) { + if (!ctx.multichainSelectedChainId) { + throw new Error(`failed request. no selected chain id`); + } + + const selectedPayment = paymentReq.data.find((p) => { + const proofs = Array.isArray(p.proof) ? p.proof : [p.proof]; + const eip712Signature2021Proof = proofs.filter( + (p) => p.type === 'EthereumEip712Signature2021' + )[0]; + if (!eip712Signature2021Proof) { + return false; + } + return eip712Signature2021Proof.eip712.domain.chainId === ctx.multichainSelectedChainId; + }); + + if (!selectedPayment) { + throw new Error( + `failed request. no payment in request for chain id ${ctx.multichainSelectedChainId}` + ); + } + + if (selectedPayment.type !== PaymentRequestDataType.Iden3PaymentRailsRequestV1) { + throw new Error(`failed request. not supported '${selectedPayment.type}' payment type `); + } + + const txId = await ctx.paymentHandler(selectedPayment); + + payments.push({ + nonce: selectedPayment.nonce, + type: PaymentType.Iden3PaymentRailsResponseV1, + paymentData: { + txId, + chainId: ctx.multichainSelectedChainId + } + }); + + continue; + } + if (paymentReq.data.type !== PaymentRequestDataType.Iden3PaymentRequestCryptoV1) { throw new Error(`failed request. not supported '${paymentReq.data.type}' payment type `); } @@ -224,7 +276,7 @@ export class PaymentHandler }); } - const paymentMessage = createPayment(senderDID, receiverDID, payments); + const paymentMessage = createPayment(senderDID, receiverDID, { payments }); const response = await this.packMessage(paymentMessage, senderDID); const agentResult = await fetch(paymentRequest.body.agent, { @@ -291,14 +343,43 @@ export class PaymentHandler for (let i = 0; i < payment.body.payments.length; i++) { const p = payment.body.payments[i]; - const paymentRequestData = opts.paymentRequest.body.payments.find((r) => r.data.id === p.id); - if (!paymentRequestData) { - throw new Error(`can't find payment request for payment id ${p.id}`); + let data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | undefined; + switch (p.type) { + case PaymentType.Iden3PaymentCryptoV1: { + data = opts.paymentRequest.body.payments.find( + (r) => (r.data as Iden3PaymentRequestCryptoV1).id === p.id + )?.data as Iden3PaymentRequestCryptoV1; + if (!data) { + throw new Error(`can't find payment request for payment id ${p.id}`); + } + break; + } + case PaymentType.Iden3PaymentRailsResponseV1: { + for (let j = 0; j < opts.paymentRequest.body.payments.length; j++) { + const paymentReq = opts.paymentRequest.body.payments[j]; + if (Array.isArray(paymentReq.data)) { + const selectedPayment = paymentReq.data.find( + (r) => (r as Iden3PaymentRailsRequestV1).nonce === p.nonce + ) as Iden3PaymentRailsRequestV1; + if (selectedPayment) { + data = selectedPayment; + break; + } + } + } + + if (!data) { + throw new Error(`can't find payment request for payment nonce ${p.nonce}`); + } + break; + } + default: + throw new Error(`failed request. not supported '${p.type}' payment type `); } if (!opts.paymentValidationHandler) { throw new Error(`please provide payment validation handler in options`); } - await opts.paymentValidationHandler(p.paymentData.txId, paymentRequestData.data); + await opts.paymentValidationHandler(p.paymentData.txId, data); } } diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index f12a3bd4..5692cf33 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -1,10 +1,5 @@ import { BasicMessage } from '../'; -import { - PaymentRequestDataType, - PaymentRequestType, - PaymentType, - SupportedCurrencies -} from '../../../verifiable'; +import { PaymentRequestType, SupportedCurrencies } from '../../../verifiable'; import { PROTOCOL_MESSAGE_TYPE } from '../../constants'; /** @beta PaymentRequestMessage is struct the represents payment-request message */ @@ -26,14 +21,14 @@ export type PaymentRequestInfo = { context: string; }[]; type: PaymentRequestType; - data: PaymentRequestDataInfo; + data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1[]; expiration?: string; description?: string; }; -/** @beta PaymentRequestDataInfo is struct the represents payment data info for payment-request */ -export type PaymentRequestDataInfo = { - type: PaymentRequestDataType; +/** @beta Iden3PaymentRequestCryptoV1 is struct the represents payment data info for payment-request */ +export type Iden3PaymentRequestCryptoV1 = { + type: 'Iden3PaymentRequestCryptoV1'; amount: string; id: string; chainId: string; @@ -42,6 +37,35 @@ export type PaymentRequestDataInfo = { signature?: string; }; +export type Iden3PaymentRailsRequestV1 = { + type: 'Iden3PaymentRailsRequestV1'; + recipient: string; + value: string; + expirationDate: string; + nonce: string; + metadata: string; + proof: EthereumEip712Signature2021 | EthereumEip712Signature2021[]; +}; + +export type EthereumEip712Signature2021 = { + type: 'EthereumEip712Signature2021'; + proofPurpose: string; + proofValue: string; + verificationMethod: string; + created: string; + eip712: { + types: string; + primaryType: string; + domain: { + name: string; + version: string; + chainId: string; + verifyingContract: string; + salt: string; + }; + }; +}; + /** @beta PaymentMessage is struct the represents payment message */ export type PaymentMessage = BasicMessage & { body: PaymentMessageBody; @@ -50,14 +74,24 @@ export type PaymentMessage = BasicMessage & { /** @beta PaymentMessageBody is struct the represents body for payment message */ export type PaymentMessageBody = { - payments: PaymentInfo[]; + payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsResponseV1)[]; }; -/** @beta PaymentInfo is struct the represents payment info for payment */ -export type PaymentInfo = { +/** @beta Iden3PaymentCryptoV1 is struct the represents payment info for payment */ +export type Iden3PaymentCryptoV1 = { id: string; - type: PaymentType; + type: 'Iden3PaymentCryptoV1'; + paymentData: { + txId: string; + }; +}; + +/** @beta Iden3PaymentRailsResponseV1 is struct the represents payment info for Iden3PaymentRailsRequestV1 */ +export type Iden3PaymentRailsResponseV1 = { + nonce: string; + type: 'Iden3PaymentRailsResponseV1'; paymentData: { txId: string; + chainId: string; }; }; diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index 53e1a247..232f5aea 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -128,7 +128,8 @@ export enum PaymentRequestType { * @enum {string} */ export enum PaymentRequestDataType { - Iden3PaymentRequestCryptoV1 = 'Iden3PaymentRequestCryptoV1' + Iden3PaymentRequestCryptoV1 = 'Iden3PaymentRequestCryptoV1', + Iden3PaymentRailsRequestV1 = 'Iden3PaymentRailsRequestV1' } /** @@ -137,7 +138,8 @@ export enum PaymentRequestDataType { * @enum {string} */ export enum PaymentType { - Iden3PaymentCryptoV1 = 'Iden3PaymentCryptoV1' + Iden3PaymentCryptoV1 = 'Iden3PaymentCryptoV1', + Iden3PaymentRailsResponseV1 = 'Iden3PaymentRailsResponseV1' } /** diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index b2196f46..d101d153 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -42,7 +42,8 @@ import { PaymentHandler } from '../../src/iden3comm/handlers/payment'; import { - PaymentRequestDataInfo, + Iden3PaymentRailsRequestV1, + Iden3PaymentRequestCryptoV1, PaymentRequestInfo } from '../../src/iden3comm/types/protocol/payment'; import { Contract, ethers, JsonRpcProvider } from 'ethers'; @@ -99,25 +100,30 @@ describe('payment-request handler', () => { const paymentIntegrationHandlerFunc = (sessionId: string, did: string) => - async (data: PaymentRequestDataInfo): Promise => { + async (data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1): Promise => { + const iden3PaymentRequestCryptoV1 = data as Iden3PaymentRequestCryptoV1; const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); - const payContract = new Contract(data.address, payContractAbi, ethSigner); - if (data.currency !== SupportedCurrencies.ETH) { + const payContract = new Contract( + iden3PaymentRequestCryptoV1.address, + payContractAbi, + ethSigner + ); + if (iden3PaymentRequestCryptoV1.currency !== SupportedCurrencies.ETH) { throw new Error('integration can only pay in eth currency'); } - const options = { value: ethers.parseUnits(data.amount, 'ether') }; + const options = { value: ethers.parseUnits(iden3PaymentRequestCryptoV1.amount, 'ether') }; const txData = await payContract.pay(sessionId, did, options); return txData.hash; }; const paymentValidationIntegrationHandlerFunc = async ( txId: string, - data: PaymentRequestDataInfo + data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 ): Promise => { const rpcProvider = new JsonRpcProvider(RPC_URL); const tx = await rpcProvider.getTransaction(txId); - if (tx?.value !== ethers.parseUnits(data.amount, 'ether')) { + if (tx?.value !== ethers.parseUnits((data as Iden3PaymentRequestCryptoV1).amount, 'ether')) { throw new Error('invalid value'); } }; @@ -230,15 +236,17 @@ describe('payment-request handler', () => { it('payment handler', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [paymentReqInfo]); - const payment = createPayment(userDID, issuerDID, [ - { - id: paymentRequest.body.payments[0].data.id, - type: PaymentType.Iden3PaymentCryptoV1, - paymentData: { - txId: '0x312312334' + const payment = createPayment(userDID, issuerDID, { + payments: [ + { + id: (paymentRequest.body.payments[0].data as Iden3PaymentRequestCryptoV1).id, + type: PaymentType.Iden3PaymentCryptoV1, + paymentData: { + txId: '0x312312334' + } } - } - ]); + ] + }); await paymentHandler.handlePayment(payment, { paymentRequest, @@ -270,15 +278,17 @@ describe('payment-request handler', () => { it.skip('payment handler (integration test)', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [paymentReqInfo]); - const payment = createPayment(userDID, issuerDID, [ - { - id: paymentRequest.body.payments[0].data.id, - type: PaymentType.Iden3PaymentCryptoV1, - paymentData: { - txId: '0xe9bea8e7adfe1092a8a4ca2cd75f4d21cc54b9b7a31bd8374b558d11b58a6a1a' + const payment = createPayment(userDID, issuerDID, { + payments: [ + { + id: (paymentRequest.body.payments[0].data as Iden3PaymentRequestCryptoV1).id, + type: PaymentType.Iden3PaymentCryptoV1, + paymentData: { + txId: '0xe9bea8e7adfe1092a8a4ca2cd75f4d21cc54b9b7a31bd8374b558d11b58a6a1a' + } } - } - ]); + ] + }); await paymentHandler.handlePayment(payment, { paymentRequest, From 83f3488c86834a6f1aac77aaecffc2185b30272a Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 2 Oct 2024 14:27:22 +0300 Subject: [PATCH 02/51] add unit tests --- src/iden3comm/handlers/payment.ts | 11 +- tests/handlers/payment.test.ts | 275 +++++++++++++++++++++++++++--- 2 files changed, 261 insertions(+), 25 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index e43f3d8d..ba6ddb20 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -2,7 +2,7 @@ import { PROTOCOL_MESSAGE_TYPE } from '../constants'; import { MediaType } from '../constants'; import { BasicMessage, IPackageManager, PackerParams } from '../types'; -import { DID } from '@iden3/js-iden3-core'; +import { DID, getChainId } from '@iden3/js-iden3-core'; import * as uuid from 'uuid'; import { proving } from '@iden3/js-jwz'; import { byteEncoder } from '../../utils'; @@ -222,9 +222,12 @@ export class PaymentHandler // if multichain request if (Array.isArray(paymentReq.data)) { - if (!ctx.multichainSelectedChainId) { - throw new Error(`failed request. no selected chain id`); - } + const issuerId = DID.idFromDID(receiverDID); + const issuerChainId = getChainId( + DID.blockchainFromId(issuerId), + DID.networkIdFromId(issuerId) + ); + ctx.multichainSelectedChainId = ctx.multichainSelectedChainId || issuerChainId.toString(); const selectedPayment = paymentReq.data.find((p) => { const proofs = Array.isArray(p.proof) ? p.proof : [p.proof]; diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index d101d153..e702c949 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -50,7 +50,7 @@ import { Contract, ethers, JsonRpcProvider } from 'ethers'; import fetchMock from '@gr2m/fetch-mock'; import { fail } from 'assert'; -describe('payment-request handler', () => { +describe.only('payment-request handler', () => { let packageMgr: IPackageManager; let paymentHandler: IPaymentHandler; let userDID, issuerDID: DID; @@ -98,23 +98,156 @@ describe('payment-request handler', () => { } ]; + const mcPayContractAbi = [ + { + inputs: [], + name: 'InvalidInitialization', + type: 'error' + }, + { + inputs: [ + { + internalType: 'string', + name: 'message', + type: 'string' + } + ], + name: 'InvalidSignature', + type: 'error' + }, + { + inputs: [], + name: 'NotInitializing', + type: 'error' + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address' + } + ], + name: 'OwnableInvalidOwner', + type: 'error' + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address' + } + ], + name: 'OwnableUnauthorizedAccount', + type: 'error' + }, + { + inputs: [ + { + internalType: 'string', + name: 'message', + type: 'string' + } + ], + name: 'PaymentError', + type: 'error' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address' + }, + { + indexed: true, + internalType: 'uint256', + name: 'nonce', + type: 'uint256' + } + ], + name: 'Payment', + type: 'event' + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address' + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'expirationDate', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'metadata', + type: 'bytes' + } + ], + internalType: 'struct MCPayment.Iden3PaymentRailsRequestV1', + name: 'paymentData', + type: 'tuple' + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes' + } + ], + name: 'pay', + outputs: [], + stateMutability: 'payable', + type: 'function' + } + ]; + const paymentIntegrationHandlerFunc = (sessionId: string, did: string) => async (data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1): Promise => { - const iden3PaymentRequestCryptoV1 = data as Iden3PaymentRequestCryptoV1; const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); - const payContract = new Contract( - iden3PaymentRequestCryptoV1.address, - payContractAbi, - ethSigner - ); - if (iden3PaymentRequestCryptoV1.currency !== SupportedCurrencies.ETH) { - throw new Error('integration can only pay in eth currency'); + if (data.type === PaymentRequestDataType.Iden3PaymentRequestCryptoV1) { + const payContract = new Contract(data.address, payContractAbi, ethSigner); + if (data.currency !== SupportedCurrencies.ETH) { + throw new Error('integration can only pay in eth currency'); + } + const options = { value: ethers.parseUnits(data.amount, 'ether') }; + const txData = await payContract.pay(sessionId, did, options); + return txData.hash; + } else if (data.type === PaymentRequestDataType.Iden3PaymentRailsRequestV1) { + const payContract = new Contract(data.proof[0].address, mcPayContractAbi, ethSigner); + const paymentData = { + recipient: data.recipient, + value: data.value, + expirationDate: data.expirationDate, + nonce: data.nonce, + metadata: data.metadata + }; + + const options = { value: data.value }; + const txData = await payContract.pay(paymentData, data.proof[0].proofValue, options); + return txData.hash; + } else { + throw new Error('invalid payment request data type'); } - const options = { value: ethers.parseUnits(iden3PaymentRequestCryptoV1.amount, 'ether') }; - const txData = await payContract.pay(sessionId, did, options); - return txData.hash; }; const paymentValidationIntegrationHandlerFunc = async ( @@ -128,7 +261,7 @@ describe('payment-request handler', () => { } }; const agent = 'https://agent-url.com'; - const paymentReqInfo: PaymentRequestInfo = { + const paymentReqCryptoV1Info: PaymentRequestInfo = { credentials: [ { type: 'AML', @@ -145,7 +278,49 @@ describe('payment-request handler', () => { currency: SupportedCurrencies.ETH }, expiration: '2125558127', - description: 'payment-request integration test' + description: 'Iden3PaymentRequestCryptoV1 payment-request integration test' + }; + + const paymentReqPaymentRailsV1Info: PaymentRequestInfo = { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + type: PaymentRequestType.PaymentRequest, + data: [ + { + type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, + recipient: '0x2C2007d72f533FfD409F0D9f515983e95bF14992', + value: '100', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), + nonce: '25', + metadata: '0x', + proof: { + type: 'EthereumEip712Signature2021', + proofPurpose: 'assertionMethod', + proofValue: + '0xa05292e9874240c5c2bbdf5a8fefff870c9fc801bde823189fc013d8ce39c7e5431bf0585f01c7e191ea7bbb7110a22e018d7f3ea0ed81a5f6a3b7b828f70f2d1c', + verificationMethod: + 'did:pkh:eip155:0:0x3e1cFE1b83E7C1CdB0c9558236c1f6C7B203C34e#blockchainAccountId', + created: new Date().toISOString(), + eip712: { + types: 'https://example.com/schemas/v1', + primaryType: 'Iden3PaymentRailsRequestV1', + domain: { + name: 'MCPayment', + version: '1.0.0', + chainId: '80002', + verifyingContract: '0xb648dCD9c6A67629387fB7226d067bC8a80dB63C', + salt: '' + } + } + } + } + ], + expiration: '2125558127', + description: 'Iden3PaymentRailsRequestV1 payment-request integration test' }; const paymentHandlerFuncMock = async (): Promise => { @@ -199,8 +374,10 @@ describe('payment-request handler', () => { fetchMock.post('https://agent-url.com', JSON.stringify(agentMessageResponse)); }); - it('payment-request handler test', async () => { - const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [paymentReqInfo]); + it('payment-request handler test (Iden3PaymentRequestCryptoV1)', async () => { + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqCryptoV1Info + ]); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, byteEncoder.encode(JSON.stringify(paymentRequest)), @@ -219,10 +396,35 @@ describe('payment-request handler', () => { ); }); + it('payment-request handler test (Iden3PaymentRailsRequestV1)', async () => { + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqPaymentRailsV1Info + ]); + const msgBytesRequest = await packageManager.pack( + MediaType.PlainMessage, + byteEncoder.encode(JSON.stringify(paymentRequest)), + {} + ); + const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { + paymentHandler: paymentHandlerFuncMock, + multichainSelectedChainId: '80002' + }); + if (!agentMessageBytes) { + fail('handlePaymentRequest is not expected null response'); + } + const { unpackedMessage: agentMessage } = await packageManager.unpack(agentMessageBytes); + + expect((agentMessage as BasicMessage).type).to.be.eq( + PROTOCOL_MESSAGE_TYPE.PROPOSAL_MESSAGE_TYPE + ); + }); + it('payment-request handler test with empty agent response', async () => { fetchMock.post('https://agent-url.com', '', { overwriteRoutes: true }); - const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [paymentReqInfo]); + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqCryptoV1Info + ]); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, byteEncoder.encode(JSON.stringify(paymentRequest)), @@ -234,8 +436,10 @@ describe('payment-request handler', () => { expect(agentMessageBytes).to.be.null; }); - it('payment handler', async () => { - const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [paymentReqInfo]); + it('payment handler (Iden3PaymentRequestCryptoV1)', async () => { + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqCryptoV1Info + ]); const payment = createPayment(userDID, issuerDID, { payments: [ { @@ -256,8 +460,35 @@ describe('payment-request handler', () => { }); }); + it('payment handler (Iden3PaymentRailsRequestV1)', async () => { + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqPaymentRailsV1Info + ]); + const payment = createPayment(userDID, issuerDID, { + payments: [ + { + nonce: (paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1).nonce, + type: PaymentType.Iden3PaymentRailsResponseV1, + paymentData: { + txId: '0x312312334', + chainId: '80002' + } + } + ] + }); + + await paymentHandler.handlePayment(payment, { + paymentRequest, + paymentValidationHandler: async () => { + Promise.resolve(); + } + }); + }); + it.skip('payment-request handler (integration test)', async () => { - const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [paymentReqInfo]); + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqCryptoV1Info + ]); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, byteEncoder.encode(JSON.stringify(paymentRequest)), @@ -277,7 +508,9 @@ describe('payment-request handler', () => { }); it.skip('payment handler (integration test)', async () => { - const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [paymentReqInfo]); + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqCryptoV1Info + ]); const payment = createPayment(userDID, issuerDID, { payments: [ { From 2304cfd5593c44cdaacbd7e1590389ab34597809 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 2 Oct 2024 15:21:46 +0300 Subject: [PATCH 03/51] add integration tests --- tests/handlers/payment.test.ts | 142 +++++++++++++++++++++++++++------ 1 file changed, 117 insertions(+), 25 deletions(-) diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index e702c949..7c4c0f58 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -233,11 +233,15 @@ describe.only('payment-request handler', () => { const txData = await payContract.pay(sessionId, did, options); return txData.hash; } else if (data.type === PaymentRequestDataType.Iden3PaymentRailsRequestV1) { - const payContract = new Contract(data.proof[0].address, mcPayContractAbi, ethSigner); + const payContract = new Contract( + data.proof[0].eip712.domain.verifyingContract, + mcPayContractAbi, + ethSigner + ); const paymentData = { recipient: data.recipient, value: data.value, - expirationDate: data.expirationDate, + expirationDate: new Date(data.expirationDate).getTime(), nonce: data.nonce, metadata: data.metadata }; @@ -256,10 +260,19 @@ describe.only('payment-request handler', () => { ): Promise => { const rpcProvider = new JsonRpcProvider(RPC_URL); const tx = await rpcProvider.getTransaction(txId); - if (tx?.value !== ethers.parseUnits((data as Iden3PaymentRequestCryptoV1).amount, 'ether')) { - throw new Error('invalid value'); + if (data.type === PaymentRequestDataType.Iden3PaymentRequestCryptoV1) { + if (tx?.value !== ethers.parseUnits(data.amount, 'ether')) { + throw new Error('invalid value'); + } + } else if (data.type === PaymentRequestDataType.Iden3PaymentRailsRequestV1) { + if (tx?.value !== BigInt(data.value)) { + throw new Error('invalid value'); + } + } else { + throw new Error('invalid payment request data type'); } }; + const agent = 'https://agent-url.com'; const paymentReqCryptoV1Info: PaymentRequestInfo = { credentials: [ @@ -292,31 +305,33 @@ describe.only('payment-request handler', () => { data: [ { type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, - recipient: '0x2C2007d72f533FfD409F0D9f515983e95bF14992', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', value: '100', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), nonce: '25', metadata: '0x', - proof: { - type: 'EthereumEip712Signature2021', - proofPurpose: 'assertionMethod', - proofValue: - '0xa05292e9874240c5c2bbdf5a8fefff870c9fc801bde823189fc013d8ce39c7e5431bf0585f01c7e191ea7bbb7110a22e018d7f3ea0ed81a5f6a3b7b828f70f2d1c', - verificationMethod: - 'did:pkh:eip155:0:0x3e1cFE1b83E7C1CdB0c9558236c1f6C7B203C34e#blockchainAccountId', - created: new Date().toISOString(), - eip712: { - types: 'https://example.com/schemas/v1', - primaryType: 'Iden3PaymentRailsRequestV1', - domain: { - name: 'MCPayment', - version: '1.0.0', - chainId: '80002', - verifyingContract: '0xb648dCD9c6A67629387fB7226d067bC8a80dB63C', - salt: '' + proof: [ + { + type: 'EthereumEip712Signature2021', + proofPurpose: 'assertionMethod', + proofValue: + '0xa05292e9874240c5c2bbdf5a8fefff870c9fc801bde823189fc013d8ce39c7e5431bf0585f01c7e191ea7bbb7110a22e018d7f3ea0ed81a5f6a3b7b828f70f2d1c', + verificationMethod: + 'did:pkh:eip155:0:0x3e1cFE1b83E7C1CdB0c9558236c1f6C7B203C34e#blockchainAccountId', + created: new Date().toISOString(), + eip712: { + types: 'https://example.com/schemas/v1', + primaryType: 'Iden3PaymentRailsRequestV1', + domain: { + name: 'MCPayment', + version: '1.0.0', + chainId: '80002', + verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + salt: '' + } } } - } + ] } ], expiration: '2125558127', @@ -485,7 +500,7 @@ describe.only('payment-request handler', () => { }); }); - it.skip('payment-request handler (integration test)', async () => { + it.skip('payment-request handler (Iden3PaymentRequestCryptoV1, integration test)', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqCryptoV1Info ]); @@ -507,7 +522,59 @@ describe.only('payment-request handler', () => { ); }); - it.skip('payment handler (integration test)', async () => { + it.skip('payment-request handler (Iden3PaymentRailsRequestV1, integration test)', async () => { + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqPaymentRailsV1Info + ]); + + // issuer prepares and signs the payment request + const nonce = BigInt(26); // change nonce for each test + const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; + data.nonce = nonce.toString(); + + const domainData = data.proof[0].eip712.domain; + delete domainData.salt; // todo: should we support salt? + const types = { + Iden3PaymentRailsRequestV1: [ + { name: 'recipient', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'expirationDate', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'metadata', type: 'bytes' } + ] + }; + const paymentData = { + recipient: data.recipient, + value: data.value, + expirationDate: new Date(data.expirationDate).getTime(), + nonce: nonce, + metadata: '0x' + }; + + const rpcProvider = new JsonRpcProvider(RPC_URL); + const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); + const signature = await ethSigner.signTypedData(domainData, types, paymentData); + data.proof[0].proofValue = signature; + + const msgBytesRequest = await packageManager.pack( + MediaType.PlainMessage, + byteEncoder.encode(JSON.stringify(paymentRequest)), + {} + ); + const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { + paymentHandler: paymentIntegrationHandlerFunc('', '') + }); + if (!agentMessageBytes) { + fail('handlePaymentRequest is not expected null response'); + } + const { unpackedMessage: agentMessage } = await packageManager.unpack(agentMessageBytes); + + expect((agentMessage as BasicMessage).type).to.be.eq( + PROTOCOL_MESSAGE_TYPE.PROPOSAL_MESSAGE_TYPE + ); + }); + + it.skip('payment handler (Iden3PaymentRequestCryptoV1, integration test)', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqCryptoV1Info ]); @@ -522,7 +589,32 @@ describe.only('payment-request handler', () => { } ] }); + await paymentHandler.handlePayment(payment, { + paymentRequest, + paymentValidationHandler: paymentValidationIntegrationHandlerFunc + }); + }); + + it.skip('payment handler (Iden3PaymentRailsRequestV1, integration test)', async () => { + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqPaymentRailsV1Info + ]); + const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; + data.nonce = '26'; + + const payment = createPayment(userDID, issuerDID, { + payments: [ + { + nonce: '26', + type: PaymentType.Iden3PaymentRailsResponseV1, + paymentData: { + txId: '0xbbfab123780717247c15a96be859bf774d582769c63044d130c77b06d850e393', + chainId: '80002' + } + } + ] + }); await paymentHandler.handlePayment(payment, { paymentRequest, paymentValidationHandler: paymentValidationIntegrationHandlerFunc From af3f1b7e19b166796361fee8e0ad0e424354ddd5 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 2 Oct 2024 15:27:54 +0300 Subject: [PATCH 04/51] use enums for types --- src/iden3comm/handlers/payment.ts | 9 +++++++-- src/iden3comm/types/protocol/payment.ts | 13 +++++++++---- src/verifiable/constants.ts | 9 +++++++++ tests/handlers/payment.test.ts | 7 ++++--- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index ba6ddb20..dca2d9ce 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -17,7 +17,12 @@ import { PaymentRequestInfo, PaymentRequestMessage } from '../types/protocol/payment'; -import { PaymentRequestDataType, PaymentRequestType, PaymentType } from '../../verifiable'; +import { + PaymentRequestDataType, + PaymentRequestType, + PaymentType, + SupportedPaymentProofType +} from '../../verifiable'; /** * @beta @@ -232,7 +237,7 @@ export class PaymentHandler const selectedPayment = paymentReq.data.find((p) => { const proofs = Array.isArray(p.proof) ? p.proof : [p.proof]; const eip712Signature2021Proof = proofs.filter( - (p) => p.type === 'EthereumEip712Signature2021' + (p) => p.type === SupportedPaymentProofType.EthereumEip712Signature2021 )[0]; if (!eip712Signature2021Proof) { return false; diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index 5692cf33..c4185d7c 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -1,5 +1,10 @@ import { BasicMessage } from '../'; -import { PaymentRequestType, SupportedCurrencies } from '../../../verifiable'; +import { + PaymentRequestDataType, + PaymentRequestType, + SupportedCurrencies, + SupportedPaymentProofType +} from '../../../verifiable'; import { PROTOCOL_MESSAGE_TYPE } from '../../constants'; /** @beta PaymentRequestMessage is struct the represents payment-request message */ @@ -28,7 +33,7 @@ export type PaymentRequestInfo = { /** @beta Iden3PaymentRequestCryptoV1 is struct the represents payment data info for payment-request */ export type Iden3PaymentRequestCryptoV1 = { - type: 'Iden3PaymentRequestCryptoV1'; + type: PaymentRequestDataType.Iden3PaymentRequestCryptoV1; amount: string; id: string; chainId: string; @@ -38,7 +43,7 @@ export type Iden3PaymentRequestCryptoV1 = { }; export type Iden3PaymentRailsRequestV1 = { - type: 'Iden3PaymentRailsRequestV1'; + type: PaymentRequestDataType.Iden3PaymentRailsRequestV1; recipient: string; value: string; expirationDate: string; @@ -48,7 +53,7 @@ export type Iden3PaymentRailsRequestV1 = { }; export type EthereumEip712Signature2021 = { - type: 'EthereumEip712Signature2021'; + type: SupportedPaymentProofType.EthereumEip712Signature2021; proofPurpose: string; proofValue: string; verificationMethod: string; diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index 232f5aea..b6384ed2 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -142,6 +142,15 @@ export enum PaymentType { Iden3PaymentRailsResponseV1 = 'Iden3PaymentRailsResponseV1' } +/** + * SupportedPaymentProofType type for payment proofs + * @beta + * @enum {string} + */ +export enum SupportedPaymentProofType { + EthereumEip712Signature2021 = 'EthereumEip712Signature2021' +} + /** * Media types for Payment supported currencies * @beta diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 7c4c0f58..80c6e530 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -16,7 +16,8 @@ import { PaymentType, BasicMessage, createProposal, - SupportedCurrencies + SupportedCurrencies, + SupportedPaymentProofType } from '../../src'; import { @@ -50,7 +51,7 @@ import { Contract, ethers, JsonRpcProvider } from 'ethers'; import fetchMock from '@gr2m/fetch-mock'; import { fail } from 'assert'; -describe.only('payment-request handler', () => { +describe('payment-request handler', () => { let packageMgr: IPackageManager; let paymentHandler: IPaymentHandler; let userDID, issuerDID: DID; @@ -312,7 +313,7 @@ describe.only('payment-request handler', () => { metadata: '0x', proof: [ { - type: 'EthereumEip712Signature2021', + type: SupportedPaymentProofType.EthereumEip712Signature2021, proofPurpose: 'assertionMethod', proofValue: '0xa05292e9874240c5c2bbdf5a8fefff870c9fc801bde823189fc013d8ce39c7e5431bf0585f01c7e191ea7bbb7110a22e018d7f3ea0ed81a5f6a3b7b828f70f2d1c', From e832465b0c693cef1a39b684da8e414610039039 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 2 Oct 2024 15:32:29 +0300 Subject: [PATCH 05/51] todo added --- tests/handlers/payment.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 80c6e530..eede093e 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -535,7 +535,7 @@ describe('payment-request handler', () => { const domainData = data.proof[0].eip712.domain; delete domainData.salt; // todo: should we support salt? - const types = { + const types = { // todo: fetch this from the `type` URL in request Iden3PaymentRailsRequestV1: [ { name: 'recipient', type: 'address' }, { name: 'value', type: 'uint256' }, From e7ebc72ad4e7fa9a2543753ae85620d64e0646b0 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 2 Oct 2024 15:40:50 +0300 Subject: [PATCH 06/51] prettier --- tests/handlers/payment.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index eede093e..6ec83bb6 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -535,7 +535,8 @@ describe('payment-request handler', () => { const domainData = data.proof[0].eip712.domain; delete domainData.salt; // todo: should we support salt? - const types = { // todo: fetch this from the `type` URL in request + const types = { + // todo: fetch this from the `type` URL in request Iden3PaymentRailsRequestV1: [ { name: 'recipient', type: 'address' }, { name: 'value', type: 'uint256' }, From 0942cde04a471a1a8704ce6c37c18ae3c12ab50a Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 3 Oct 2024 17:23:48 +0300 Subject: [PATCH 07/51] createPaymentRailsV1 function --- src/iden3comm/handlers/payment.ts | 147 ++++++++++++++++++++++++++---- tests/handlers/payment.test.ts | 145 ++++++++++++++--------------- 2 files changed, 198 insertions(+), 94 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index dca2d9ce..21f2c5ad 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -2,7 +2,7 @@ import { PROTOCOL_MESSAGE_TYPE } from '../constants'; import { MediaType } from '../constants'; import { BasicMessage, IPackageManager, PackerParams } from '../types'; -import { DID, getChainId } from '@iden3/js-iden3-core'; +import { DID } from '@iden3/js-iden3-core'; import * as uuid from 'uuid'; import { proving } from '@iden3/js-jwz'; import { byteEncoder } from '../../utils'; @@ -13,7 +13,6 @@ import { Iden3PaymentRailsResponseV1, Iden3PaymentRequestCryptoV1, PaymentMessage, - PaymentMessageBody, PaymentRequestInfo, PaymentRequestMessage } from '../types/protocol/payment'; @@ -23,6 +22,7 @@ import { PaymentType, SupportedPaymentProofType } from '../../verifiable'; +import { Signer } from 'ethers'; /** * @beta @@ -55,6 +55,112 @@ export function createPaymentRequest( return request; } +/** + * @beta + * createPaymentRailsV1 is a function to create protocol payment message + * @param {DID} sender - sender did + * @param {DID} receiver - receiver did + * @param {Signer} signer - receiver did + * @param {string} agent - agent URL + * @param opts - payment options + * @returns {Promise} + */ +export async function createPaymentRailsV1( + sender: DID, + receiver: DID, + agent: string, + signer: Signer, + opts: { + payments: [ + { + credentials: { + type: string; + context: string; + }[]; + expiration?: Date; + description?: string; + chains: { + nonce: bigint; + value: bigint; + chainId: string; + recipient: string; + verifyingContract: string; + expirationDate?: Date; + }[]; + } + ]; + } +): Promise { + const payments: PaymentRequestInfo[] = []; + for (let i = 0; i < opts.payments.length; i++) { + const { credentials, expiration, description } = opts.payments[i]; + const dataArr: Iden3PaymentRailsRequestV1[] = []; + for (let j = 0; j < opts.payments[i].chains.length; j++) { + const { nonce, value, chainId, recipient, verifyingContract, expirationDate } = + opts.payments[i].chains[j]; + + const types = { + // todo: fetch this from the `type` URL in request + Iden3PaymentRailsRequestV1: [ + { name: 'recipient', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'expirationDate', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'metadata', type: 'bytes' } + ] + }; + const paymentData = { + recipient, + value, + expirationDate: expirationDate?.getTime() ?? 0, + nonce, + metadata: '0x' + }; + + const domain = { + name: 'MCPayment', + version: '1.0.0', + chainId, + verifyingContract + }; + const signature = await signer.signTypedData(domain, types, paymentData); + dataArr.push({ + type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, + recipient, + value: value.toString(), + expirationDate: expirationDate?.toISOString() ?? '', + nonce: nonce.toString(), + metadata: '0x', + proof: [ + { + type: SupportedPaymentProofType.EthereumEip712Signature2021, + proofPurpose: 'assertionMethod', + proofValue: signature, + verificationMethod: `did:pkh:eip155:${chainId}:${recipient}#blockchainAccountId`, + created: new Date().toISOString(), + eip712: { + types: 'https://example.com/schemas/v1', // todo: type here + primaryType: 'Iden3PaymentRailsRequestV1', + domain: { + ...domain, + salt: '' + } + } + } + ] + }); + } + payments.push({ + type: PaymentRequestType.PaymentRequest, + data: dataArr, + credentials, + expiration: expiration?.toISOString(), + description + }); + } + return createPaymentRequest(sender, receiver, agent, payments); +} + /** * @beta * createPayment is a function to create protocol payment message @@ -66,7 +172,7 @@ export function createPaymentRequest( export function createPayment( sender: DID, receiver: DID, - body: PaymentMessageBody + payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsResponseV1)[] ): PaymentMessage { const uuidv4 = uuid.v4(); const request: PaymentMessage = { @@ -76,7 +182,9 @@ export function createPayment( to: receiver.string(), typ: MediaType.PlainMessage, type: PROTOCOL_MESSAGE_TYPE.PAYMENT_MESSAGE_TYPE, - body + body: { + payments + } }; return request; } @@ -225,24 +333,25 @@ export class PaymentHandler throw new Error(`failed request. not supported '${paymentReq.type}' payment type `); } + if (paymentReq.expiration && new Date(paymentReq.expiration) < new Date()) { + throw new Error(`failed request. expired request`); + } + // if multichain request if (Array.isArray(paymentReq.data)) { - const issuerId = DID.idFromDID(receiverDID); - const issuerChainId = getChainId( - DID.blockchainFromId(issuerId), - DID.networkIdFromId(issuerId) - ); - ctx.multichainSelectedChainId = ctx.multichainSelectedChainId || issuerChainId.toString(); - + if (!ctx.multichainSelectedChainId) { + throw new Error(`failed request. please provide multichainSelectedChainId`); + } const selectedPayment = paymentReq.data.find((p) => { const proofs = Array.isArray(p.proof) ? p.proof : [p.proof]; - const eip712Signature2021Proof = proofs.filter( - (p) => p.type === SupportedPaymentProofType.EthereumEip712Signature2021 - )[0]; - if (!eip712Signature2021Proof) { - return false; - } - return eip712Signature2021Proof.eip712.domain.chainId === ctx.multichainSelectedChainId; + return ( + proofs.filter( + (p) => + p.type === SupportedPaymentProofType.EthereumEip712Signature2021 && + p.eip712.domain.chainId === ctx.multichainSelectedChainId + )[0] && + (!p.expirationDate || new Date(p.expirationDate) > new Date()) + ); }); if (!selectedPayment) { @@ -284,7 +393,7 @@ export class PaymentHandler }); } - const paymentMessage = createPayment(senderDID, receiverDID, { payments }); + const paymentMessage = createPayment(senderDID, receiverDID, payments); const response = await this.packMessage(paymentMessage, senderDID); const agentResult = await fetch(paymentRequest.body.agent, { diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 6ec83bb6..7e015db0 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -38,6 +38,7 @@ import { MediaType, PROTOCOL_MESSAGE_TYPE } from '../../src/iden3comm/constants' import { DID } from '@iden3/js-iden3-core'; import { createPayment, + createPaymentRailsV1, createPaymentRequest, IPaymentHandler, PaymentHandler @@ -456,17 +457,15 @@ describe('payment-request handler', () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqCryptoV1Info ]); - const payment = createPayment(userDID, issuerDID, { - payments: [ - { - id: (paymentRequest.body.payments[0].data as Iden3PaymentRequestCryptoV1).id, - type: PaymentType.Iden3PaymentCryptoV1, - paymentData: { - txId: '0x312312334' - } + const payment = createPayment(userDID, issuerDID, [ + { + id: (paymentRequest.body.payments[0].data as Iden3PaymentRequestCryptoV1).id, + type: PaymentType.Iden3PaymentCryptoV1, + paymentData: { + txId: '0x312312334' } - ] - }); + } + ]); await paymentHandler.handlePayment(payment, { paymentRequest, @@ -480,18 +479,16 @@ describe('payment-request handler', () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqPaymentRailsV1Info ]); - const payment = createPayment(userDID, issuerDID, { - payments: [ - { - nonce: (paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1).nonce, - type: PaymentType.Iden3PaymentRailsResponseV1, - paymentData: { - txId: '0x312312334', - chainId: '80002' - } + const payment = createPayment(userDID, issuerDID, [ + { + nonce: (paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1).nonce, + type: PaymentType.Iden3PaymentRailsResponseV1, + paymentData: { + txId: '0x312312334', + chainId: '80002' } - ] - }); + } + ]); await paymentHandler.handlePayment(payment, { paymentRequest, @@ -524,39 +521,40 @@ describe('payment-request handler', () => { }); it.skip('payment-request handler (Iden3PaymentRailsRequestV1, integration test)', async () => { - const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ - paymentReqPaymentRailsV1Info - ]); - - // issuer prepares and signs the payment request - const nonce = BigInt(26); // change nonce for each test - const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; - data.nonce = nonce.toString(); - - const domainData = data.proof[0].eip712.domain; - delete domainData.salt; // todo: should we support salt? - const types = { - // todo: fetch this from the `type` URL in request - Iden3PaymentRailsRequestV1: [ - { name: 'recipient', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'expirationDate', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'metadata', type: 'bytes' } - ] - }; - const paymentData = { - recipient: data.recipient, - value: data.value, - expirationDate: new Date(data.expirationDate).getTime(), - nonce: nonce, - metadata: '0x' - }; - const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); - const signature = await ethSigner.signTypedData(domainData, types, paymentData); - data.proof[0].proofValue = signature; + const paymentRequest = await createPaymentRailsV1(issuerDID, userDID, agent, ethSigner, { + payments: [ + { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + description: 'Iden3PaymentRailsRequestV1 payment-request integration test', + expiration: new Date(new Date().setHours(new Date().getHours() + 1)), + chains: [ + { + nonce: 32n, + value: 100n, + chainId: '80002', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + }, + { + nonce: 44n, + value: 10000n, + chainId: '1101', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + } + ] + } + ] + }); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, @@ -564,7 +562,8 @@ describe('payment-request handler', () => { {} ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { - paymentHandler: paymentIntegrationHandlerFunc('', '') + paymentHandler: paymentIntegrationHandlerFunc('', ''), + multichainSelectedChainId: '80002' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -580,17 +579,15 @@ describe('payment-request handler', () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqCryptoV1Info ]); - const payment = createPayment(userDID, issuerDID, { - payments: [ - { - id: (paymentRequest.body.payments[0].data as Iden3PaymentRequestCryptoV1).id, - type: PaymentType.Iden3PaymentCryptoV1, - paymentData: { - txId: '0xe9bea8e7adfe1092a8a4ca2cd75f4d21cc54b9b7a31bd8374b558d11b58a6a1a' - } + const payment = createPayment(userDID, issuerDID, [ + { + id: (paymentRequest.body.payments[0].data as Iden3PaymentRequestCryptoV1).id, + type: PaymentType.Iden3PaymentCryptoV1, + paymentData: { + txId: '0xe9bea8e7adfe1092a8a4ca2cd75f4d21cc54b9b7a31bd8374b558d11b58a6a1a' } - ] - }); + } + ]); await paymentHandler.handlePayment(payment, { paymentRequest, paymentValidationHandler: paymentValidationIntegrationHandlerFunc @@ -603,20 +600,18 @@ describe('payment-request handler', () => { ]); const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; - data.nonce = '26'; + data.nonce = '28'; - const payment = createPayment(userDID, issuerDID, { - payments: [ - { - nonce: '26', - type: PaymentType.Iden3PaymentRailsResponseV1, - paymentData: { - txId: '0xbbfab123780717247c15a96be859bf774d582769c63044d130c77b06d850e393', - chainId: '80002' - } + const payment = createPayment(userDID, issuerDID, [ + { + nonce: data.nonce, + type: PaymentType.Iden3PaymentRailsResponseV1, + paymentData: { + txId: '0xea5d9f4396d403b3e88b13fba4f2e5e12347488a76f08544c6bc1efc1961de4c', + chainId: '80002' } - ] - }); + } + ]); await paymentHandler.handlePayment(payment, { paymentRequest, paymentValidationHandler: paymentValidationIntegrationHandlerFunc From b638140bf9fa9c23d47adec9a0650d50e280b3e7 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 4 Oct 2024 17:58:58 +0300 Subject: [PATCH 08/51] set typeUrl Iden3PaymentRailsRequestV1.json --- src/iden3comm/handlers/payment.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 21f2c5ad..d64e8e14 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -99,16 +99,10 @@ export async function createPaymentRailsV1( const { nonce, value, chainId, recipient, verifyingContract, expirationDate } = opts.payments[i].chains[j]; - const types = { - // todo: fetch this from the `type` URL in request - Iden3PaymentRailsRequestV1: [ - { name: 'recipient', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'expirationDate', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'metadata', type: 'bytes' } - ] - }; + const typeUrl = 'https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json'; + const typesFetchResult = await fetch(typeUrl); + const types = await typesFetchResult.json() + delete types.EIP712Domain; const paymentData = { recipient, value, @@ -139,7 +133,7 @@ export async function createPaymentRailsV1( verificationMethod: `did:pkh:eip155:${chainId}:${recipient}#blockchainAccountId`, created: new Date().toISOString(), eip712: { - types: 'https://example.com/schemas/v1', // todo: type here + types: typeUrl, primaryType: 'Iden3PaymentRailsRequestV1', domain: { ...domain, From 1830190efe4b1dfac511abbd896b5fd43a02b80e Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 4 Oct 2024 17:59:56 +0300 Subject: [PATCH 09/51] format --- src/iden3comm/handlers/payment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index d64e8e14..8c9b17d4 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -101,7 +101,7 @@ export async function createPaymentRailsV1( const typeUrl = 'https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json'; const typesFetchResult = await fetch(typeUrl); - const types = await typesFetchResult.json() + const types = await typesFetchResult.json(); delete types.EIP712Domain; const paymentData = { recipient, From 091bff4a504f55a6afa65ac3d60688c5f07b174e Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 4 Oct 2024 20:53:27 +0300 Subject: [PATCH 10/51] check recipient --- src/iden3comm/handlers/payment.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 8c9b17d4..ac465fff 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -99,6 +99,9 @@ export async function createPaymentRailsV1( const { nonce, value, chainId, recipient, verifyingContract, expirationDate } = opts.payments[i].chains[j]; + if (recipient !== await signer.getAddress()) { + throw new Error('recipient is not the signer'); + } const typeUrl = 'https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json'; const typesFetchResult = await fetch(typeUrl); const types = await typesFetchResult.json(); From a78b1ac81cf4251b97bf4c1afac104baf0d8d8e7 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 4 Oct 2024 20:54:51 +0300 Subject: [PATCH 11/51] format --- src/iden3comm/handlers/payment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index ac465fff..7bcc17e4 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -99,7 +99,7 @@ export async function createPaymentRailsV1( const { nonce, value, chainId, recipient, verifyingContract, expirationDate } = opts.payments[i].chains[j]; - if (recipient !== await signer.getAddress()) { + if (recipient !== (await signer.getAddress())) { throw new Error('recipient is not the signer'); } const typeUrl = 'https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json'; From cc6b1f1290f48fe2e2bf44d22cd193d3f299d65c Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 11 Oct 2024 13:20:49 +0300 Subject: [PATCH 12/51] add currencty and rename value to amount --- .eslintrc.js | 2 +- src/iden3comm/handlers/payment.ts | 15 +++++++++++---- src/iden3comm/types/protocol/payment.ts | 3 ++- src/verifiable/constants.ts | 1 + tests/handlers/payment.test.ts | 15 +++++++++------ 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0c22a8c9..ba866a70 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,7 +10,7 @@ module.exports = { ...spellcheckerRule, cspell: { ...cspellConfig, - ignoreWords: ['unmarshal', 'JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw', 'gdwj', 'fwor', 'multichain'] + ignoreWords: ['unmarshal', 'JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw', 'gdwj', 'fwor', 'multichain', "ETHWEI"] } } ] diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 7bcc17e4..3869c6bf 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -20,6 +20,7 @@ import { PaymentRequestDataType, PaymentRequestType, PaymentType, + SupportedCurrencies, SupportedPaymentProofType } from '../../verifiable'; import { Signer } from 'ethers'; @@ -81,7 +82,8 @@ export async function createPaymentRailsV1( description?: string; chains: { nonce: bigint; - value: bigint; + amount: bigint; + currency: SupportedCurrencies; chainId: string; recipient: string; verifyingContract: string; @@ -96,7 +98,7 @@ export async function createPaymentRailsV1( const { credentials, expiration, description } = opts.payments[i]; const dataArr: Iden3PaymentRailsRequestV1[] = []; for (let j = 0; j < opts.payments[i].chains.length; j++) { - const { nonce, value, chainId, recipient, verifyingContract, expirationDate } = + const { nonce, amount, currency, chainId, recipient, verifyingContract, expirationDate } = opts.payments[i].chains[j]; if (recipient !== (await signer.getAddress())) { @@ -108,7 +110,7 @@ export async function createPaymentRailsV1( delete types.EIP712Domain; const paymentData = { recipient, - value, + amount, expirationDate: expirationDate?.getTime() ?? 0, nonce, metadata: '0x' @@ -124,7 +126,8 @@ export async function createPaymentRailsV1( dataArr.push({ type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, recipient, - value: value.toString(), + amount: amount.toString(), + currency, expirationDate: expirationDate?.toISOString() ?? '', nonce: nonce.toString(), metadata: '0x', @@ -361,6 +364,10 @@ export class PaymentHandler throw new Error(`failed request. not supported '${selectedPayment.type}' payment type `); } + if (selectedPayment.currency !== SupportedCurrencies.ETHWEI) { + throw new Error(`failed request. not supported '${selectedPayment.currency}' currency `); + } + const txId = await ctx.paymentHandler(selectedPayment); payments.push({ diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index c4185d7c..3e23d207 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -45,7 +45,8 @@ export type Iden3PaymentRequestCryptoV1 = { export type Iden3PaymentRailsRequestV1 = { type: PaymentRequestDataType.Iden3PaymentRailsRequestV1; recipient: string; - value: string; + amount: string; + currency: SupportedCurrencies; expirationDate: string; nonce: string; metadata: string; diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index b6384ed2..42c76653 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -158,6 +158,7 @@ export enum SupportedPaymentProofType { */ export enum SupportedCurrencies { ETH = 'ETH', + ETHWEI = 'ETHWEI', MATIC = 'MATIC' } diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 7e015db0..66f16dab 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -242,13 +242,13 @@ describe('payment-request handler', () => { ); const paymentData = { recipient: data.recipient, - value: data.value, + amount: data.amount, expirationDate: new Date(data.expirationDate).getTime(), nonce: data.nonce, metadata: data.metadata }; - const options = { value: data.value }; + const options = { value: data.amount }; const txData = await payContract.pay(paymentData, data.proof[0].proofValue, options); return txData.hash; } else { @@ -267,7 +267,7 @@ describe('payment-request handler', () => { throw new Error('invalid value'); } } else if (data.type === PaymentRequestDataType.Iden3PaymentRailsRequestV1) { - if (tx?.value !== BigInt(data.value)) { + if (tx?.value !== BigInt(data.amount)) { throw new Error('invalid value'); } } else { @@ -308,7 +308,8 @@ describe('payment-request handler', () => { { type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - value: '100', + amount: '100', + currency: SupportedCurrencies.ETHWEI, expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), nonce: '25', metadata: '0x', @@ -537,7 +538,8 @@ describe('payment-request handler', () => { chains: [ { nonce: 32n, - value: 100n, + amount: 100n, + currency: SupportedCurrencies.ETHWEI, chainId: '80002', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', @@ -545,7 +547,8 @@ describe('payment-request handler', () => { }, { nonce: 44n, - value: 10000n, + amount: 10000n, + currency: SupportedCurrencies.ETHWEI, chainId: '1101', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', From 78a56499562034314d089d44bf822415289557d6 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 11 Oct 2024 13:28:56 +0300 Subject: [PATCH 13/51] add supported in the msg --- src/iden3comm/handlers/payment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 3869c6bf..24f5d8a1 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -365,7 +365,7 @@ export class PaymentHandler } if (selectedPayment.currency !== SupportedCurrencies.ETHWEI) { - throw new Error(`failed request. not supported '${selectedPayment.currency}' currency `); + throw new Error(`failed request. not supported '${selectedPayment.currency}' currency. Only ${SupportedCurrencies.ETHWEI} is supported`); } const txId = await ctx.paymentHandler(selectedPayment); From da5b90de3eb7742917d9bcc7dbc07cfd57c7176f Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 11 Oct 2024 13:31:02 +0300 Subject: [PATCH 14/51] format --- src/iden3comm/handlers/payment.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 24f5d8a1..c259032e 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -365,7 +365,9 @@ export class PaymentHandler } if (selectedPayment.currency !== SupportedCurrencies.ETHWEI) { - throw new Error(`failed request. not supported '${selectedPayment.currency}' currency. Only ${SupportedCurrencies.ETHWEI} is supported`); + throw new Error( + `failed request. not supported '${selectedPayment.currency}' currency. Only ${SupportedCurrencies.ETHWEI} is supported` + ); } const txId = await ctx.paymentHandler(selectedPayment); From 0f228de09b0e69dfc347b520e962e973d5a45cf2 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 11 Oct 2024 13:43:32 +0300 Subject: [PATCH 15/51] remove expiration --- src/iden3comm/handlers/payment.ts | 4 +--- tests/handlers/payment.test.ts | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index c259032e..62c53708 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -78,7 +78,6 @@ export async function createPaymentRailsV1( type: string; context: string; }[]; - expiration?: Date; description?: string; chains: { nonce: bigint; @@ -95,7 +94,7 @@ export async function createPaymentRailsV1( ): Promise { const payments: PaymentRequestInfo[] = []; for (let i = 0; i < opts.payments.length; i++) { - const { credentials, expiration, description } = opts.payments[i]; + const { credentials, description } = opts.payments[i]; const dataArr: Iden3PaymentRailsRequestV1[] = []; for (let j = 0; j < opts.payments[i].chains.length; j++) { const { nonce, amount, currency, chainId, recipient, verifyingContract, expirationDate } = @@ -154,7 +153,6 @@ export async function createPaymentRailsV1( type: PaymentRequestType.PaymentRequest, data: dataArr, credentials, - expiration: expiration?.toISOString(), description }); } diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 66f16dab..183a22c0 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -534,7 +534,6 @@ describe('payment-request handler', () => { } ], description: 'Iden3PaymentRailsRequestV1 payment-request integration test', - expiration: new Date(new Date().setHours(new Date().getHours() + 1)), chains: [ { nonce: 32n, From 3680ef8ff916f1a272461212b08e19c4da5eb649 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 11 Oct 2024 14:00:38 +0300 Subject: [PATCH 16/51] Iden3PaymentRequestCryptoV1 expiration --- src/iden3comm/handlers/payment.ts | 8 ++++---- src/iden3comm/types/protocol/payment.ts | 2 +- tests/handlers/payment.test.ts | 5 ++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 62c53708..0a56f36d 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -331,10 +331,6 @@ export class PaymentHandler throw new Error(`failed request. not supported '${paymentReq.type}' payment type `); } - if (paymentReq.expiration && new Date(paymentReq.expiration) < new Date()) { - throw new Error(`failed request. expired request`); - } - // if multichain request if (Array.isArray(paymentReq.data)) { if (!ctx.multichainSelectedChainId) { @@ -386,6 +382,10 @@ export class PaymentHandler throw new Error(`failed request. not supported '${paymentReq.data.type}' payment type `); } + if (paymentReq.data.expiration && new Date(paymentReq.data.expiration) < new Date()) { + throw new Error(`failed request. expired request`); + } + const txId = await ctx.paymentHandler(paymentReq.data); payments.push({ diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index 3e23d207..623cac9a 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -27,7 +27,6 @@ export type PaymentRequestInfo = { }[]; type: PaymentRequestType; data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1[]; - expiration?: string; description?: string; }; @@ -40,6 +39,7 @@ export type Iden3PaymentRequestCryptoV1 = { address: string; currency: SupportedCurrencies; signature?: string; + expiration?: string; }; export type Iden3PaymentRailsRequestV1 = { diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 183a22c0..b0182715 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -290,9 +290,9 @@ describe('payment-request handler', () => { id: '12432', chainId: '80002', address: '0x2C2007d72f533FfD409F0D9f515983e95bF14992', - currency: SupportedCurrencies.ETH + currency: SupportedCurrencies.ETH, + expiration: '2125558127', }, - expiration: '2125558127', description: 'Iden3PaymentRequestCryptoV1 payment-request integration test' }; @@ -337,7 +337,6 @@ describe('payment-request handler', () => { ] } ], - expiration: '2125558127', description: 'Iden3PaymentRailsRequestV1 payment-request integration test' }; From ff4c638ef3a99b803ee41318bb05566e1713f0ba Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 11 Oct 2024 14:02:29 +0300 Subject: [PATCH 17/51] format --- tests/handlers/payment.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index b0182715..89efb636 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -291,7 +291,7 @@ describe('payment-request handler', () => { chainId: '80002', address: '0x2C2007d72f533FfD409F0D9f515983e95bF14992', currency: SupportedCurrencies.ETH, - expiration: '2125558127', + expiration: '2125558127' }, description: 'Iden3PaymentRequestCryptoV1 payment-request integration test' }; From 99c77fc040f0b2bfc51364df6fab1a5a6f8a44b8 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Mon, 14 Oct 2024 15:34:21 +0300 Subject: [PATCH 18/51] rename Iden3PaymentRailsResponseV1 to Iden3PaymentRailsV1 --- src/iden3comm/handlers/payment.ts | 10 +++++----- src/iden3comm/types/protocol/payment.ts | 8 ++++---- src/verifiable/constants.ts | 2 +- tests/handlers/payment.test.ts | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 0a56f36d..f208734c 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -10,7 +10,7 @@ import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handl import { Iden3PaymentCryptoV1, Iden3PaymentRailsRequestV1, - Iden3PaymentRailsResponseV1, + Iden3PaymentRailsV1, Iden3PaymentRequestCryptoV1, PaymentMessage, PaymentRequestInfo, @@ -170,7 +170,7 @@ export async function createPaymentRailsV1( export function createPayment( sender: DID, receiver: DID, - payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsResponseV1)[] + payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1)[] ): PaymentMessage { const uuidv4 = uuid.v4(); const request: PaymentMessage = { @@ -324,7 +324,7 @@ export class PaymentHandler const senderDID = DID.parse(paymentRequest.to); const receiverDID = DID.parse(paymentRequest.from); - const payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsResponseV1)[] = []; + const payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1)[] = []; for (let i = 0; i < paymentRequest.body.payments.length; i++) { const paymentReq = paymentRequest.body.payments[i]; if (paymentReq.type !== PaymentRequestType.PaymentRequest) { @@ -368,7 +368,7 @@ export class PaymentHandler payments.push({ nonce: selectedPayment.nonce, - type: PaymentType.Iden3PaymentRailsResponseV1, + type: PaymentType.Iden3PaymentRailsV1, paymentData: { txId, chainId: ctx.multichainSelectedChainId @@ -475,7 +475,7 @@ export class PaymentHandler } break; } - case PaymentType.Iden3PaymentRailsResponseV1: { + case PaymentType.Iden3PaymentRailsV1: { for (let j = 0; j < opts.paymentRequest.body.payments.length; j++) { const paymentReq = opts.paymentRequest.body.payments[j]; if (Array.isArray(paymentReq.data)) { diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index 623cac9a..66f942b9 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -80,7 +80,7 @@ export type PaymentMessage = BasicMessage & { /** @beta PaymentMessageBody is struct the represents body for payment message */ export type PaymentMessageBody = { - payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsResponseV1)[]; + payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1)[]; }; /** @beta Iden3PaymentCryptoV1 is struct the represents payment info for payment */ @@ -92,10 +92,10 @@ export type Iden3PaymentCryptoV1 = { }; }; -/** @beta Iden3PaymentRailsResponseV1 is struct the represents payment info for Iden3PaymentRailsRequestV1 */ -export type Iden3PaymentRailsResponseV1 = { +/** @beta Iden3PaymentRailsV1 is struct the represents payment info for Iden3PaymentRailsRequestV1 */ +export type Iden3PaymentRailsV1 = { nonce: string; - type: 'Iden3PaymentRailsResponseV1'; + type: 'Iden3PaymentRailsV1'; paymentData: { txId: string; chainId: string; diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index 42c76653..0dba39da 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -139,7 +139,7 @@ export enum PaymentRequestDataType { */ export enum PaymentType { Iden3PaymentCryptoV1 = 'Iden3PaymentCryptoV1', - Iden3PaymentRailsResponseV1 = 'Iden3PaymentRailsResponseV1' + Iden3PaymentRailsV1 = 'Iden3PaymentRailsV1' } /** diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 89efb636..cd8e6782 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -482,7 +482,7 @@ describe('payment-request handler', () => { const payment = createPayment(userDID, issuerDID, [ { nonce: (paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1).nonce, - type: PaymentType.Iden3PaymentRailsResponseV1, + type: PaymentType.Iden3PaymentRailsV1, paymentData: { txId: '0x312312334', chainId: '80002' @@ -606,7 +606,7 @@ describe('payment-request handler', () => { const payment = createPayment(userDID, issuerDID, [ { nonce: data.nonce, - type: PaymentType.Iden3PaymentRailsResponseV1, + type: PaymentType.Iden3PaymentRailsV1, paymentData: { txId: '0xea5d9f4396d403b3e88b13fba4f2e5e12347488a76f08544c6bc1efc1961de4c', chainId: '80002' From f60110038f6f93606eec9cf5064c49ad50a31cc4 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Mon, 14 Oct 2024 16:21:49 +0300 Subject: [PATCH 19/51] remove top lvl PaymentRequest type field --- src/iden3comm/handlers/payment.ts | 5 ----- src/iden3comm/types/protocol/payment.ts | 2 -- src/verifiable/constants.ts | 8 -------- tests/handlers/payment.test.ts | 2 -- 4 files changed, 17 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index f208734c..79a5787d 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -18,7 +18,6 @@ import { } from '../types/protocol/payment'; import { PaymentRequestDataType, - PaymentRequestType, PaymentType, SupportedCurrencies, SupportedPaymentProofType @@ -150,7 +149,6 @@ export async function createPaymentRailsV1( }); } payments.push({ - type: PaymentRequestType.PaymentRequest, data: dataArr, credentials, description @@ -327,9 +325,6 @@ export class PaymentHandler const payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1)[] = []; for (let i = 0; i < paymentRequest.body.payments.length; i++) { const paymentReq = paymentRequest.body.payments[i]; - if (paymentReq.type !== PaymentRequestType.PaymentRequest) { - throw new Error(`failed request. not supported '${paymentReq.type}' payment type `); - } // if multichain request if (Array.isArray(paymentReq.data)) { diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index 66f942b9..d2fe8b9c 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -1,7 +1,6 @@ import { BasicMessage } from '../'; import { PaymentRequestDataType, - PaymentRequestType, SupportedCurrencies, SupportedPaymentProofType } from '../../../verifiable'; @@ -25,7 +24,6 @@ export type PaymentRequestInfo = { type: string; context: string; }[]; - type: PaymentRequestType; data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1[]; description?: string; }; diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index 0dba39da..4b5a24b4 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -113,14 +113,6 @@ export enum RefreshServiceType { Iden3RefreshService2023 = 'Iden3RefreshService2023' } -/** - * PaymentRequestType type for payment requests - * @beta - * @enum {string} - */ -export enum PaymentRequestType { - PaymentRequest = 'PaymentRequest' -} /** * PaymentRequestDataType type for payment requests diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index cd8e6782..a209395e 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -283,7 +283,6 @@ describe('payment-request handler', () => { context: 'http://test.com' } ], - type: PaymentRequestType.PaymentRequest, data: { type: PaymentRequestDataType.Iden3PaymentRequestCryptoV1, amount: '0.001', @@ -303,7 +302,6 @@ describe('payment-request handler', () => { context: 'http://test.com' } ], - type: PaymentRequestType.PaymentRequest, data: [ { type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, From cddfeac0ac1a920db240e74945c658b801252826 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Mon, 14 Oct 2024 16:23:18 +0300 Subject: [PATCH 20/51] format --- src/verifiable/constants.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index 4b5a24b4..963a45d7 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -113,7 +113,6 @@ export enum RefreshServiceType { Iden3RefreshService2023 = 'Iden3RefreshService2023' } - /** * PaymentRequestDataType type for payment requests * @beta From dd9a219a057c189afdf08498ffb8606c29df76d7 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Mon, 14 Oct 2024 16:33:46 +0300 Subject: [PATCH 21/51] fix tests --- tests/handlers/payment.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index a209395e..ea15c191 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -10,7 +10,6 @@ import { CircuitId, PlainPacker, PackageManager, - PaymentRequestType, PaymentRequestDataType, byteEncoder, PaymentType, From f237306e85f93f9faa55e81cf5a3f2063972e8ae Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Tue, 15 Oct 2024 15:39:51 +0300 Subject: [PATCH 22/51] add @context field to payment messages --- src/iden3comm/handlers/payment.ts | 6 ++++++ src/iden3comm/types/protocol/payment.ts | 4 ++++ tests/handlers/payment.test.ts | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 79a5787d..202cb82f 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -123,6 +123,10 @@ export async function createPaymentRailsV1( const signature = await signer.signTypedData(domain, types, paymentData); dataArr.push({ type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, + '@context': [ + 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1', + 'https://w3id.org/security/suites/eip712sig-2021/v1' + ], recipient, amount: amount.toString(), currency, @@ -364,6 +368,7 @@ export class PaymentHandler payments.push({ nonce: selectedPayment.nonce, type: PaymentType.Iden3PaymentRailsV1, + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { txId, chainId: ctx.multichainSelectedChainId @@ -385,6 +390,7 @@ export class PaymentHandler payments.push({ id: paymentReq.data.id, + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', type: PaymentType.Iden3PaymentCryptoV1, paymentData: { txId diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index d2fe8b9c..3a95ae39 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -31,6 +31,7 @@ export type PaymentRequestInfo = { /** @beta Iden3PaymentRequestCryptoV1 is struct the represents payment data info for payment-request */ export type Iden3PaymentRequestCryptoV1 = { type: PaymentRequestDataType.Iden3PaymentRequestCryptoV1; + '@context'?: string | (string | object)[]; amount: string; id: string; chainId: string; @@ -42,6 +43,7 @@ export type Iden3PaymentRequestCryptoV1 = { export type Iden3PaymentRailsRequestV1 = { type: PaymentRequestDataType.Iden3PaymentRailsRequestV1; + '@context': string | (string | object)[]; recipient: string; amount: string; currency: SupportedCurrencies; @@ -85,6 +87,7 @@ export type PaymentMessageBody = { export type Iden3PaymentCryptoV1 = { id: string; type: 'Iden3PaymentCryptoV1'; + '@context'?: string | (string | object)[]; paymentData: { txId: string; }; @@ -94,6 +97,7 @@ export type Iden3PaymentCryptoV1 = { export type Iden3PaymentRailsV1 = { nonce: string; type: 'Iden3PaymentRailsV1'; + '@context': string | (string | object)[]; paymentData: { txId: string; chainId: string; diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index ea15c191..c505076f 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -304,6 +304,10 @@ describe('payment-request handler', () => { data: [ { type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, + '@context': [ + 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1', + 'https://w3id.org/security/suites/eip712sig-2021/v1' + ], recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', amount: '100', currency: SupportedCurrencies.ETHWEI, @@ -480,6 +484,7 @@ describe('payment-request handler', () => { { nonce: (paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1).nonce, type: PaymentType.Iden3PaymentRailsV1, + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { txId: '0x312312334', chainId: '80002' @@ -604,6 +609,7 @@ describe('payment-request handler', () => { { nonce: data.nonce, type: PaymentType.Iden3PaymentRailsV1, + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { txId: '0xea5d9f4396d403b3e88b13fba4f2e5e12347488a76f08544c6bc1efc1961de4c', chainId: '80002' From 23ebd25e5127227f9ab587aadcc0c896702d952a Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 24 Oct 2024 12:21:19 +0300 Subject: [PATCH 23/51] add Iden3PaymentRailsERC20RequestV1 --- src/iden3comm/handlers/payment.ts | 220 ++++++- src/iden3comm/types/protocol/payment.ts | 22 +- src/verifiable/constants.ts | 9 +- tests/handlers/payment.test.ts | 727 ++++++++++++++++++++++-- 4 files changed, 932 insertions(+), 46 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 202cb82f..9770c789 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -9,6 +9,8 @@ import { byteEncoder } from '../../utils'; import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler'; import { Iden3PaymentCryptoV1, + Iden3PaymentRailsERC20RequestV1, + Iden3PaymentRailsERC20V1, Iden3PaymentRailsRequestV1, Iden3PaymentRailsV1, Iden3PaymentRequestCryptoV1, @@ -161,6 +163,170 @@ export async function createPaymentRailsV1( return createPaymentRequest(sender, receiver, agent, payments); } +/** + * @beta + * createPaymentRailsV1 is a function to create protocol payment message + * @param {DID} sender - sender did + * @param {DID} receiver - receiver did + * @param {Signer} signer - receiver did + * @param {string} agent - agent URL + * @param opts - payment options + * @returns {Promise} + */ +export async function createERC20PaymentRailsV1( + sender: DID, + receiver: DID, + agent: string, + signer: Signer, + opts: { + payments: [ + { + credentials: { + type: string; + context: string; + }[]; + description?: string; + chains: { + tokenAddress: string; + nonce: bigint; + amount: bigint; + currency: SupportedCurrencies; + chainId: string; + recipient: string; + verifyingContract: string; + expirationDate?: Date; + }[]; + } + ]; + } +): Promise { + const payments: PaymentRequestInfo[] = []; + for (let i = 0; i < opts.payments.length; i++) { + const { credentials, description } = opts.payments[i]; + const dataArr: Iden3PaymentRailsERC20RequestV1[] = []; + for (let j = 0; j < opts.payments[i].chains.length; j++) { + const { + tokenAddress, + nonce, + amount, + currency, + chainId, + recipient, + verifyingContract, + expirationDate + } = opts.payments[i].chains[j]; + + if (recipient !== (await signer.getAddress())) { + throw new Error('recipient is not the signer'); + } + const typeUrl = 'https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json'; // todo: REPLACE TO NEW URL + const typesFetchResult = await fetch(typeUrl); + let types = await typesFetchResult.json(); + + types = { + EIP712Domain: [ + { + name: 'name', + type: 'string' + }, + { + name: 'version', + type: 'string' + }, + { + name: 'chainId', + type: 'uint256' + }, + { + name: 'verifyingContract', + 'type:': 'address' + } + ], + Iden3PaymentRailsRequestV1: [ + { + name: 'tokenAddress', + type: 'address' + }, + { + name: 'recipient', + type: 'address' + }, + { + name: 'amount', + type: 'uint256' + }, + { + name: 'expirationDate', + type: 'uint256' + }, + { + name: 'nonce', + type: 'uint256' + }, + { + name: 'metadata', + type: 'bytes' + } + ] + }; + delete types.EIP712Domain; + const paymentData = { + tokenAddress, + recipient, + amount, + expirationDate: expirationDate?.getTime() ?? 0, + nonce, + metadata: '0x' + }; + + const domain = { + name: 'MCPayment', + version: '1.0.0', + chainId, + verifyingContract + }; + const signature = await signer.signTypedData(domain, types, paymentData); + dataArr.push({ + type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1, + '@context': [ + 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1', + 'https://w3id.org/security/suites/eip712sig-2021/v1' + ], + tokenAddress, + recipient, + amount: amount.toString(), + currency, + expirationDate: expirationDate?.toISOString() ?? '', + nonce: nonce.toString(), + metadata: '0x', + proof: [ + { + type: SupportedPaymentProofType.EthereumEip712Signature2021, + proofPurpose: 'assertionMethod', + proofValue: signature, + verificationMethod: `did:pkh:eip155:${chainId}:${recipient}#blockchainAccountId`, + created: new Date().toISOString(), + eip712: { + types: typeUrl, + primaryType: 'Iden3PaymentRailsRequestV1', + domain: { + ...domain, + salt: '' + } + } + } + ] + }); + } + payments.push({ + data: dataArr, + credentials, + description + }); + } + return createPaymentRequest(sender, receiver, agent, payments); +} + /** * @beta * createPayment is a function to create protocol payment message @@ -172,7 +338,7 @@ export async function createPaymentRailsV1( export function createPayment( sender: DID, receiver: DID, - payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1)[] + payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1 | Iden3PaymentRailsERC20V1)[] ): PaymentMessage { const uuidv4 = uuid.v4(); const request: PaymentMessage = { @@ -229,9 +395,13 @@ export interface IPaymentHandler { /** @beta PaymentRequestMessageHandlerOptions represents payment-request handler options */ export type PaymentRequestMessageHandlerOptions = { paymentHandler: ( - data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 + data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 ) => Promise; multichainSelectedChainId?: string; + selectedPaymentType?: + | PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 + | PaymentRequestDataType.Iden3PaymentRailsRequestV1; + erc20TokenApproveHandler?: (data: Iden3PaymentRailsERC20RequestV1) => Promise; }; /** @beta PaymentHandlerOptions represents payment handler options */ @@ -326,7 +496,7 @@ export class PaymentHandler const senderDID = DID.parse(paymentRequest.to); const receiverDID = DID.parse(paymentRequest.from); - const payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1)[] = []; + const payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1 | Iden3PaymentRailsERC20V1)[] = []; for (let i = 0; i < paymentRequest.body.payments.length; i++) { const paymentReq = paymentRequest.body.payments[i]; @@ -335,7 +505,7 @@ export class PaymentHandler if (!ctx.multichainSelectedChainId) { throw new Error(`failed request. please provide multichainSelectedChainId`); } - const selectedPayment = paymentReq.data.find((p) => { + const selectedPayments = paymentReq.data.filter((p) => { const proofs = Array.isArray(p.proof) ? p.proof : [p.proof]; return ( proofs.filter( @@ -347,27 +517,59 @@ export class PaymentHandler ); }); - if (!selectedPayment) { + if (!selectedPayments.length) { throw new Error( `failed request. no payment in request for chain id ${ctx.multichainSelectedChainId}` ); } - if (selectedPayment.type !== PaymentRequestDataType.Iden3PaymentRailsRequestV1) { - throw new Error(`failed request. not supported '${selectedPayment.type}' payment type `); + let selectedPayment = selectedPayments[0]; + if (ctx.selectedPaymentType) { + const selectedPaymentByType = selectedPayments.find( + (p) => p.type === ctx.selectedPaymentType + ); + if (!selectedPaymentByType) { + throw new Error( + `failed request. no payment in request for chain id ${ctx.multichainSelectedChainId} and type ${ctx.selectedPaymentType}` + ); + } + selectedPayment = selectedPaymentByType; + } + + if ( + selectedPayment.type !== PaymentRequestDataType.Iden3PaymentRailsRequestV1 && + selectedPayment.type !== PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 + ) { + throw new Error( + `failed request. not supported '${selectedPayment['type']}' payment type ` + ); } - if (selectedPayment.currency !== SupportedCurrencies.ETHWEI) { + if ( + selectedPayment.currency !== SupportedCurrencies.ETHWEI && + selectedPayment.currency !== SupportedCurrencies.ERC20Token + ) { throw new Error( `failed request. not supported '${selectedPayment.currency}' currency. Only ${SupportedCurrencies.ETHWEI} is supported` ); } + if (selectedPayment.type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1) { + if (!ctx.erc20TokenApproveHandler) { + throw new Error(`please provide erc20TokenApproveHandler in context`); + } + + await ctx.erc20TokenApproveHandler(selectedPayment); + } + const txId = await ctx.paymentHandler(selectedPayment); payments.push({ nonce: selectedPayment.nonce, - type: PaymentType.Iden3PaymentRailsV1, + type: + selectedPayment.type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 + ? PaymentType.Iden3PaymentRailsERC20V1 + : PaymentType.Iden3PaymentRailsV1, '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { txId, diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index 3a95ae39..87d71727 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -24,7 +24,9 @@ export type PaymentRequestInfo = { type: string; context: string; }[]; - data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1[]; + data: + | Iden3PaymentRequestCryptoV1 + | (Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1)[]; description?: string; }; @@ -53,6 +55,11 @@ export type Iden3PaymentRailsRequestV1 = { proof: EthereumEip712Signature2021 | EthereumEip712Signature2021[]; }; +export type Iden3PaymentRailsERC20RequestV1 = Omit, 'type'> & { + tokenAddress: string; + type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1; +}; + export type EthereumEip712Signature2021 = { type: SupportedPaymentProofType.EthereumEip712Signature2021; proofPurpose: string; @@ -80,7 +87,7 @@ export type PaymentMessage = BasicMessage & { /** @beta PaymentMessageBody is struct the represents body for payment message */ export type PaymentMessageBody = { - payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1)[]; + payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1 | Iden3PaymentRailsERC20V1)[]; }; /** @beta Iden3PaymentCryptoV1 is struct the represents payment info for payment */ @@ -103,3 +110,14 @@ export type Iden3PaymentRailsV1 = { chainId: string; }; }; + +/** @beta Iden3PaymentRailsERC20V1 is struct the represents payment info for Iden3PaymentRailsERC20RequestV1 */ +export type Iden3PaymentRailsERC20V1 = { + nonce: string; + type: 'Iden3PaymentRailsERC20V1'; + '@context': string | (string | object)[]; + paymentData: { + txId: string; + chainId: string; + }; +}; diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index 963a45d7..a4722145 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -120,7 +120,8 @@ export enum RefreshServiceType { */ export enum PaymentRequestDataType { Iden3PaymentRequestCryptoV1 = 'Iden3PaymentRequestCryptoV1', - Iden3PaymentRailsRequestV1 = 'Iden3PaymentRailsRequestV1' + Iden3PaymentRailsRequestV1 = 'Iden3PaymentRailsRequestV1', + Iden3PaymentRailsERC20RequestV1 = 'Iden3PaymentRailsERC20RequestV1' } /** @@ -130,7 +131,8 @@ export enum PaymentRequestDataType { */ export enum PaymentType { Iden3PaymentCryptoV1 = 'Iden3PaymentCryptoV1', - Iden3PaymentRailsV1 = 'Iden3PaymentRailsV1' + Iden3PaymentRailsV1 = 'Iden3PaymentRailsV1', + Iden3PaymentRailsERC20V1 = 'Iden3PaymentRailsERC20V1' } /** @@ -150,7 +152,8 @@ export enum SupportedPaymentProofType { export enum SupportedCurrencies { ETH = 'ETH', ETHWEI = 'ETHWEI', - MATIC = 'MATIC' + MATIC = 'MATIC', + ERC20Token = 'ERC20Token' } /** diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index c505076f..6191d5ee 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -36,6 +36,7 @@ import path from 'path'; import { MediaType, PROTOCOL_MESSAGE_TYPE } from '../../src/iden3comm/constants'; import { DID } from '@iden3/js-iden3-core'; import { + createERC20PaymentRailsV1, createPayment, createPaymentRailsV1, createPaymentRequest, @@ -43,6 +44,7 @@ import { PaymentHandler } from '../../src/iden3comm/handlers/payment'; import { + Iden3PaymentRailsERC20RequestV1, Iden3PaymentRailsRequestV1, Iden3PaymentRequestCryptoV1, PaymentRequestInfo @@ -105,6 +107,17 @@ describe('payment-request handler', () => { name: 'InvalidInitialization', type: 'error' }, + { + inputs: [ + { + internalType: 'string', + name: 'message', + type: 'string' + } + ], + name: 'InvalidOwnerPercentage', + type: 'error' + }, { inputs: [ { @@ -118,60 +131,532 @@ describe('payment-request handler', () => { }, { inputs: [], - name: 'NotInitializing', - type: 'error' - }, - { - inputs: [ + name: 'NotInitializing', + type: 'error' + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address' + } + ], + name: 'OwnableInvalidOwner', + type: 'error' + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address' + } + ], + name: 'OwnableUnauthorizedAccount', + type: 'error' + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address' + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256' + }, + { + internalType: 'string', + name: 'message', + type: 'string' + } + ], + name: 'PaymentError', + type: 'error' + }, + { + inputs: [ + { + internalType: 'string', + name: 'message', + type: 'string' + } + ], + name: 'WithdrawError', + type: 'error' + }, + { + anonymous: false, + inputs: [], + name: 'EIP712DomainChanged', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'version', + type: 'uint64' + } + ], + name: 'Initialized', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address' + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address' + } + ], + name: 'OwnershipTransferStarted', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address' + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address' + } + ], + name: 'OwnershipTransferred', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address' + }, + { + indexed: true, + internalType: 'uint256', + name: 'nonce', + type: 'uint256' + } + ], + name: 'Payment', + type: 'event' + }, + { + inputs: [], + name: 'ERC_20_PAYMENT_DATA_TYPE_HASH', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'PAYMENT_DATA_TYPE_HASH', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'VERSION', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'acceptOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'eip712Domain', + outputs: [ + { + internalType: 'bytes1', + name: 'fields', + type: 'bytes1' + }, + { + internalType: 'string', + name: 'name', + type: 'string' + }, + { + internalType: 'string', + name: 'version', + type: 'string' + }, + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256' + }, + { + internalType: 'address', + name: 'verifyingContract', + type: 'address' + }, + { + internalType: 'bytes32', + name: 'salt', + type: 'bytes32' + }, + { + internalType: 'uint256[]', + name: 'extensions', + type: 'uint256[]' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'tokenAddress', + type: 'address' + }, + { + internalType: 'address', + name: 'recipient', + type: 'address' + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'expirationDate', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'metadata', + type: 'bytes' + } + ], + internalType: 'struct MCPayment.Iden3PaymentRailsERC20RequestV1', + name: 'paymentData', + type: 'tuple' + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes' + } + ], + name: 'erc20Payment', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address' + } + ], + name: 'getBalance', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'getOwnerBalance', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'getOwnerPercentage', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address' + }, + { + internalType: 'uint8', + name: 'ownerPercentage', + type: 'uint8' + } + ], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address' + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256' + } + ], + name: 'isPaymentDone', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'issuerWithdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'ownerWithdraw', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address' + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'expirationDate', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'metadata', + type: 'bytes' + } + ], + internalType: 'struct MCPayment.Iden3PaymentRailsRequestV1', + name: 'paymentData', + type: 'tuple' + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes' + } + ], + name: 'pay', + outputs: [], + stateMutability: 'payable', + type: 'function' + }, + { + inputs: [], + name: 'pendingOwner', + outputs: [ { internalType: 'address', - name: 'owner', + name: '', type: 'address' } ], - name: 'OwnableInvalidOwner', - type: 'error' + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' }, { inputs: [ { internalType: 'address', - name: 'account', + name: 'newOwner', type: 'address' } ], - name: 'OwnableUnauthorizedAccount', - type: 'error' + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' }, { inputs: [ { - internalType: 'string', - name: 'message', - type: 'string' + internalType: 'uint8', + name: 'ownerPercentage', + type: 'uint8' } ], - name: 'PaymentError', - type: 'error' + name: 'updateOwnerPercentage', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' }, { - anonymous: false, inputs: [ { - indexed: true, - internalType: 'address', - name: 'recipient', - type: 'address' + components: [ + { + internalType: 'address', + name: 'tokenAddress', + type: 'address' + }, + { + internalType: 'address', + name: 'recipient', + type: 'address' + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'expirationDate', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'metadata', + type: 'bytes' + } + ], + internalType: 'struct MCPayment.Iden3PaymentRailsERC20RequestV1', + name: 'paymentData', + type: 'tuple' }, { - indexed: true, - internalType: 'uint256', - name: 'nonce', - type: 'uint256' + internalType: 'bytes', + name: 'signature', + type: 'bytes' } ], - name: 'Payment', - type: 'event' + name: 'verifyERC20Signature', + outputs: [], + stateMutability: 'view', + type: 'function' }, { inputs: [ @@ -184,7 +669,7 @@ describe('payment-request handler', () => { }, { internalType: 'uint256', - name: 'value', + name: 'amount', type: 'uint256' }, { @@ -213,16 +698,21 @@ describe('payment-request handler', () => { type: 'bytes' } ], - name: 'pay', + name: 'verifySignature', outputs: [], - stateMutability: 'payable', + stateMutability: 'view', type: 'function' } ]; const paymentIntegrationHandlerFunc = (sessionId: string, did: string) => - async (data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1): Promise => { + async ( + data: + | Iden3PaymentRequestCryptoV1 + | Iden3PaymentRailsRequestV1 + | Iden3PaymentRailsERC20RequestV1 + ): Promise => { const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); if (data.type === PaymentRequestDataType.Iden3PaymentRequestCryptoV1) { @@ -247,6 +737,24 @@ describe('payment-request handler', () => { metadata: data.metadata }; + const options = { value: data.amount }; + const txData = await payContract.pay(paymentData, data.proof[0].proofValue, options); + return txData.hash; + } else if (data.type == PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1) { + const payContract = new Contract( + data.proof[0].eip712.domain.verifyingContract, + mcPayContractAbi, + ethSigner + ); + const paymentData = { + tokenAddress: data.tokenAddress, + recipient: data.recipient, + amount: data.amount, + expirationDate: new Date(data.expirationDate).getTime(), + nonce: data.nonce, + metadata: data.metadata + }; + const options = { value: data.amount }; const txData = await payContract.pay(paymentData, data.proof[0].proofValue, options); return txData.hash; @@ -341,6 +849,54 @@ describe('payment-request handler', () => { description: 'Iden3PaymentRailsRequestV1 payment-request integration test' }; + const paymentReqPaymentRailsERC20V1Info: PaymentRequestInfo = { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + data: [ + { + type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1, + '@context': [ + 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1', + 'https://w3id.org/security/suites/eip712sig-2021/v1' + ], + tokenAddress: '0x2C2007d72f533FfD409F0D9f515983e95bF14992', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + amount: '100', + currency: SupportedCurrencies.ETHWEI, + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), + nonce: '25', + metadata: '0x', + proof: [ + { + type: SupportedPaymentProofType.EthereumEip712Signature2021, + proofPurpose: 'assertionMethod', + proofValue: + '0xa05292e9874240c5c2bbdf5a8fefff870c9fc801bde823189fc013d8ce39c7e5431bf0585f01c7e191ea7bbb7110a22e018d7f3ea0ed81a5f6a3b7b828f70f2d1c', + verificationMethod: + 'did:pkh:eip155:0:0x3e1cFE1b83E7C1CdB0c9558236c1f6C7B203C34e#blockchainAccountId', + created: new Date().toISOString(), + eip712: { + types: 'https://example.com/schemas/v1', + primaryType: 'Iden3PaymentRailsRequestV1', + domain: { + name: 'MCPayment', + version: '1.0.0', + chainId: '80002', + verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + salt: '' + } + } + } + ] + } + ], + description: 'Iden3PaymentRailsRequestV1 payment-request integration test' + }; + const paymentHandlerFuncMock = async (): Promise => { return Promise.resolve('0x312312334'); }; @@ -437,6 +993,31 @@ describe('payment-request handler', () => { ); }); + it('payment-request handler test (Iden3PaymentRailsERC20RequestV1)', async () => { + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqPaymentRailsERC20V1Info + ]); + const msgBytesRequest = await packageManager.pack( + MediaType.PlainMessage, + byteEncoder.encode(JSON.stringify(paymentRequest)), + {} + ); + const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { + paymentHandler: paymentHandlerFuncMock, + multichainSelectedChainId: '80002', + selectedPaymentType: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1, + erc20TokenApproveHandler: () => Promise.resolve('0x312312334') + }); + if (!agentMessageBytes) { + fail('handlePaymentRequest is not expected null response'); + } + const { unpackedMessage: agentMessage } = await packageManager.unpack(agentMessageBytes); + + expect((agentMessage as BasicMessage).type).to.be.eq( + PROTOCOL_MESSAGE_TYPE.PROPOSAL_MESSAGE_TYPE + ); + }); + it('payment-request handler test with empty agent response', async () => { fetchMock.post('https://agent-url.com', '', { overwriteRoutes: true }); @@ -476,7 +1057,7 @@ describe('payment-request handler', () => { }); }); - it('payment handler (Iden3PaymentRailsRequestV1)', async () => { + it('payment handler (Iden3PaymentRailsERC20RequestV1)', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqPaymentRailsV1Info ]); @@ -500,6 +1081,30 @@ describe('payment-request handler', () => { }); }); + it('payment handler (paymentReqPaymentRailsERC20V1Info)', async () => { + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqPaymentRailsERC20V1Info + ]); + const payment = createPayment(userDID, issuerDID, [ + { + nonce: (paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1).nonce, + type: PaymentType.Iden3PaymentRailsV1, + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', + paymentData: { + txId: '0x312312334', + chainId: '80002' + } + } + ]); + + await paymentHandler.handlePayment(payment, { + paymentRequest, + paymentValidationHandler: async () => { + Promise.resolve(); + } + }); + }); + it.skip('payment-request handler (Iden3PaymentRequestCryptoV1, integration test)', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqCryptoV1Info @@ -578,6 +1183,64 @@ describe('payment-request handler', () => { ); }); + it.skip('payment-request handler (Iden3PaymentRailsERC20RequestV1, integration test)', async () => { + const rpcProvider = new JsonRpcProvider(RPC_URL); + const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); + const paymentRequest = await createERC20PaymentRailsV1(issuerDID, userDID, agent, ethSigner, { + payments: [ + { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + description: 'Iden3PaymentRailsRequestV1 payment-request integration test', + chains: [ + { + tokenAddress: '', + nonce: 32n, + amount: 100n, + currency: SupportedCurrencies.ERC20Token, + chainId: '80002', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + }, + { + tokenAddress: '', + nonce: 44n, + amount: 10000n, + currency: SupportedCurrencies.ERC20Token, + chainId: '1101', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + } + ] + } + ] + }); + + const msgBytesRequest = await packageManager.pack( + MediaType.PlainMessage, + byteEncoder.encode(JSON.stringify(paymentRequest)), + {} + ); + const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { + paymentHandler: paymentIntegrationHandlerFunc('', ''), + multichainSelectedChainId: '80002' + }); + if (!agentMessageBytes) { + fail('handlePaymentRequest is not expected null response'); + } + const { unpackedMessage: agentMessage } = await packageManager.unpack(agentMessageBytes); + + expect((agentMessage as BasicMessage).type).to.be.eq( + PROTOCOL_MESSAGE_TYPE.PROPOSAL_MESSAGE_TYPE + ); + }); + it.skip('payment handler (Iden3PaymentRequestCryptoV1, integration test)', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqCryptoV1Info From 740ef3c45d93935c410475474584c27e3835046c Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 24 Oct 2024 13:49:21 +0300 Subject: [PATCH 24/51] add integration tests --- src/iden3comm/handlers/payment.ts | 2 +- tests/handlers/payment.test.ts | 581 +++++------------------------- 2 files changed, 90 insertions(+), 493 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 9770c789..81f3989d 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -242,7 +242,7 @@ export async function createERC20PaymentRailsV1( 'type:': 'address' } ], - Iden3PaymentRailsRequestV1: [ + Iden3PaymentRailsERC20RequestV1: [ { name: 'tokenAddress', type: 'address' diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 6191d5ee..2399e3b2 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -102,257 +102,6 @@ describe('payment-request handler', () => { ]; const mcPayContractAbi = [ - { - inputs: [], - name: 'InvalidInitialization', - type: 'error' - }, - { - inputs: [ - { - internalType: 'string', - name: 'message', - type: 'string' - } - ], - name: 'InvalidOwnerPercentage', - type: 'error' - }, - { - inputs: [ - { - internalType: 'string', - name: 'message', - type: 'string' - } - ], - name: 'InvalidSignature', - type: 'error' - }, - { - inputs: [], - name: 'NotInitializing', - type: 'error' - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address' - } - ], - name: 'OwnableInvalidOwner', - type: 'error' - }, - { - inputs: [ - { - internalType: 'address', - name: 'account', - type: 'address' - } - ], - name: 'OwnableUnauthorizedAccount', - type: 'error' - }, - { - inputs: [ - { - internalType: 'address', - name: 'recipient', - type: 'address' - }, - { - internalType: 'uint256', - name: 'nonce', - type: 'uint256' - }, - { - internalType: 'string', - name: 'message', - type: 'string' - } - ], - name: 'PaymentError', - type: 'error' - }, - { - inputs: [ - { - internalType: 'string', - name: 'message', - type: 'string' - } - ], - name: 'WithdrawError', - type: 'error' - }, - { - anonymous: false, - inputs: [], - name: 'EIP712DomainChanged', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { - indexed: false, - internalType: 'uint64', - name: 'version', - type: 'uint64' - } - ], - name: 'Initialized', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'previousOwner', - type: 'address' - }, - { - indexed: true, - internalType: 'address', - name: 'newOwner', - type: 'address' - } - ], - name: 'OwnershipTransferStarted', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'previousOwner', - type: 'address' - }, - { - indexed: true, - internalType: 'address', - name: 'newOwner', - type: 'address' - } - ], - name: 'OwnershipTransferred', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'recipient', - type: 'address' - }, - { - indexed: true, - internalType: 'uint256', - name: 'nonce', - type: 'uint256' - } - ], - name: 'Payment', - type: 'event' - }, - { - inputs: [], - name: 'ERC_20_PAYMENT_DATA_TYPE_HASH', - outputs: [ - { - internalType: 'bytes32', - name: '', - type: 'bytes32' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'PAYMENT_DATA_TYPE_HASH', - outputs: [ - { - internalType: 'bytes32', - name: '', - type: 'bytes32' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'VERSION', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'acceptOwnership', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [], - name: 'eip712Domain', - outputs: [ - { - internalType: 'bytes1', - name: 'fields', - type: 'bytes1' - }, - { - internalType: 'string', - name: 'name', - type: 'string' - }, - { - internalType: 'string', - name: 'version', - type: 'string' - }, - { - internalType: 'uint256', - name: 'chainId', - type: 'uint256' - }, - { - internalType: 'address', - name: 'verifyingContract', - type: 'address' - }, - { - internalType: 'bytes32', - name: 'salt', - type: 'bytes32' - }, - { - internalType: 'uint256[]', - name: 'extensions', - type: 'uint256[]' - } - ], - stateMutability: 'view', - type: 'function' - }, { inputs: [ { @@ -403,69 +152,6 @@ describe('payment-request handler', () => { stateMutability: 'nonpayable', type: 'function' }, - { - inputs: [ - { - internalType: 'address', - name: 'recipient', - type: 'address' - } - ], - name: 'getBalance', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'getOwnerBalance', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'getOwnerPercentage', - outputs: [ - { - internalType: 'uint8', - name: '', - type: 'uint8' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address' - }, - { - internalType: 'uint8', - name: 'ownerPercentage', - type: 'uint8' - } - ], - name: 'initialize', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, { inputs: [ { @@ -490,33 +176,6 @@ describe('payment-request handler', () => { stateMutability: 'view', type: 'function' }, - { - inputs: [], - name: 'issuerWithdraw', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [], - name: 'owner', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'ownerWithdraw', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, { inputs: [ { @@ -561,146 +220,32 @@ describe('payment-request handler', () => { outputs: [], stateMutability: 'payable', type: 'function' - }, - { - inputs: [], - name: 'pendingOwner', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'renounceOwnership', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, + } + ]; + + const erc20Abi = [ { inputs: [ { internalType: 'address', - name: 'newOwner', + name: 'spender', type: 'address' - } - ], - name: 'transferOwnership', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - internalType: 'uint8', - name: 'ownerPercentage', - type: 'uint8' - } - ], - name: 'updateOwnerPercentage', - outputs: [], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { - components: [ - { - internalType: 'address', - name: 'tokenAddress', - type: 'address' - }, - { - internalType: 'address', - name: 'recipient', - type: 'address' - }, - { - internalType: 'uint256', - name: 'amount', - type: 'uint256' - }, - { - internalType: 'uint256', - name: 'expirationDate', - type: 'uint256' - }, - { - internalType: 'uint256', - name: 'nonce', - type: 'uint256' - }, - { - internalType: 'bytes', - name: 'metadata', - type: 'bytes' - } - ], - internalType: 'struct MCPayment.Iden3PaymentRailsERC20RequestV1', - name: 'paymentData', - type: 'tuple' }, { - internalType: 'bytes', - name: 'signature', - type: 'bytes' + internalType: 'uint256', + name: 'value', + type: 'uint256' } ], - name: 'verifyERC20Signature', - outputs: [], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { - components: [ - { - internalType: 'address', - name: 'recipient', - type: 'address' - }, - { - internalType: 'uint256', - name: 'amount', - type: 'uint256' - }, - { - internalType: 'uint256', - name: 'expirationDate', - type: 'uint256' - }, - { - internalType: 'uint256', - name: 'nonce', - type: 'uint256' - }, - { - internalType: 'bytes', - name: 'metadata', - type: 'bytes' - } - ], - internalType: 'struct MCPayment.Iden3PaymentRailsRequestV1', - name: 'paymentData', - type: 'tuple' - }, + name: 'approve', + outputs: [ { - internalType: 'bytes', - name: 'signature', - type: 'bytes' + internalType: 'bool', + name: '', + type: 'bool' } ], - name: 'verifySignature', - outputs: [], - stateMutability: 'view', + stateMutability: 'nonpayable', type: 'function' } ]; @@ -755,8 +300,7 @@ describe('payment-request handler', () => { metadata: data.metadata }; - const options = { value: data.amount }; - const txData = await payContract.pay(paymentData, data.proof[0].proofValue, options); + const txData = await payContract.erc20Payment(paymentData, data.proof[0].proofValue); return txData.hash; } else { throw new Error('invalid payment request data type'); @@ -765,7 +309,7 @@ describe('payment-request handler', () => { const paymentValidationIntegrationHandlerFunc = async ( txId: string, - data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 + data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 ): Promise => { const rpcProvider = new JsonRpcProvider(RPC_URL); const tx = await rpcProvider.getTransaction(txId); @@ -777,6 +321,25 @@ describe('payment-request handler', () => { if (tx?.value !== BigInt(data.amount)) { throw new Error('invalid value'); } + const payContract = new Contract( + data.proof[0].eip712.domain.verifyingContract, + mcPayContractAbi, + rpcProvider + ); + const isSuccess = await payContract.isPaymentDone(data.recipient, data.nonce); + if (!isSuccess) { + throw new Error('payment failed'); + } + } else if (data.type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1) { + const payContract = new Contract( + data.proof[0].eip712.domain.verifyingContract, + mcPayContractAbi, + rpcProvider + ); + const isSuccess = await payContract.isPaymentDone(data.recipient, data.nonce); + if (!isSuccess) { + throw new Error('payment failed'); + } } else { throw new Error('invalid payment request data type'); } @@ -820,7 +383,7 @@ describe('payment-request handler', () => { amount: '100', currency: SupportedCurrencies.ETHWEI, expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), - nonce: '25', + nonce: '132', metadata: '0x', proof: [ { @@ -838,7 +401,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d', salt: '' } } @@ -863,12 +426,12 @@ describe('payment-request handler', () => { 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1', 'https://w3id.org/security/suites/eip712sig-2021/v1' ], - tokenAddress: '0x2C2007d72f533FfD409F0D9f515983e95bF14992', + tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - amount: '100', - currency: SupportedCurrencies.ETHWEI, + amount: '1', + currency: SupportedCurrencies.ERC20Token, expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), - nonce: '25', + nonce: '32', metadata: '0x', proof: [ { @@ -886,7 +449,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + verifyingContract: '0xCCc1640E846b12578E00F2E17e361c1728cb949D', salt: '' } } @@ -1142,12 +705,12 @@ describe('payment-request handler', () => { description: 'Iden3PaymentRailsRequestV1 payment-request integration test', chains: [ { - nonce: 32n, + nonce: 132n, amount: 100n, currency: SupportedCurrencies.ETHWEI, chainId: '80002', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) }, { @@ -1156,7 +719,7 @@ describe('payment-request handler', () => { currency: SupportedCurrencies.ETHWEI, chainId: '1101', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) } ] @@ -1195,26 +758,26 @@ describe('payment-request handler', () => { context: 'http://test.com' } ], - description: 'Iden3PaymentRailsRequestV1 payment-request integration test', + description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', chains: [ { - tokenAddress: '', - nonce: 32n, - amount: 100n, + tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', + nonce: 39n, + amount: 30n, currency: SupportedCurrencies.ERC20Token, chainId: '80002', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + verifyingContract: '0xCCc1640E846b12578E00F2E17e361c1728cb949D', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) }, { - tokenAddress: '', + tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', nonce: 44n, - amount: 10000n, + amount: 30n, currency: SupportedCurrencies.ERC20Token, chainId: '1101', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x8e08d46D77a06CeF290268a5553669f165751c70', + verifyingContract: '0xCCc1640E846b12578E00F2E17e361c1728cb949D', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) } ] @@ -1229,7 +792,16 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentIntegrationHandlerFunc('', ''), - multichainSelectedChainId: '80002' + multichainSelectedChainId: '80002', + erc20TokenApproveHandler: async (data: Iden3PaymentRailsERC20RequestV1) => { + const token = new Contract(data.tokenAddress, erc20Abi, ethSigner); + const txData = await token.approve( + data.proof[0].eip712.domain.verifyingContract, + data.amount + ); + await txData.wait(3); + return txData.hash; + } }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -1266,7 +838,7 @@ describe('payment-request handler', () => { ]); const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; - data.nonce = '28'; + data.nonce = '132'; const payment = createPayment(userDID, issuerDID, [ { @@ -1284,4 +856,29 @@ describe('payment-request handler', () => { paymentValidationHandler: paymentValidationIntegrationHandlerFunc }); }); + + it.skip('payment handler (Iden3PaymentRailsERC20RequestV1, integration test)', async () => { + const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ + paymentReqPaymentRailsERC20V1Info + ]); + + const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; + data.nonce = '39'; + + const payment = createPayment(userDID, issuerDID, [ + { + nonce: data.nonce, + type: PaymentType.Iden3PaymentRailsV1, + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', + paymentData: { + txId: '0x72de0354aee61a9083424a4b852ec80db4f236e31b63345dc3efefc3b197ecca', + chainId: '80002' + } + } + ]); + await paymentHandler.handlePayment(payment, { + paymentRequest, + paymentValidationHandler: paymentValidationIntegrationHandlerFunc + }); + }); }); From f93764f850b6b98f7007f182a101bf90a4f71e54 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 24 Oct 2024 14:19:15 +0300 Subject: [PATCH 25/51] extend handlePayment --- src/iden3comm/handlers/payment.ts | 11 ++++++++--- tests/handlers/payment.test.ts | 10 +++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 81f3989d..5e844eb5 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -409,7 +409,7 @@ export type PaymentHandlerOptions = { paymentRequest: PaymentRequestMessage; paymentValidationHandler: ( txId: string, - data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 + data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 ) => Promise; }; @@ -667,7 +667,11 @@ export class PaymentHandler for (let i = 0; i < payment.body.payments.length; i++) { const p = payment.body.payments[i]; - let data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | undefined; + let data: + | Iden3PaymentRequestCryptoV1 + | Iden3PaymentRailsRequestV1 + | Iden3PaymentRailsERC20RequestV1 + | undefined; switch (p.type) { case PaymentType.Iden3PaymentCryptoV1: { data = opts.paymentRequest.body.payments.find( @@ -678,7 +682,8 @@ export class PaymentHandler } break; } - case PaymentType.Iden3PaymentRailsV1: { + case PaymentType.Iden3PaymentRailsV1: + case PaymentType.Iden3PaymentRailsERC20V1: { for (let j = 0; j < opts.paymentRequest.body.payments.length; j++) { const paymentReq = opts.paymentRequest.body.payments[j]; if (Array.isArray(paymentReq.data)) { diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 2399e3b2..19ae54d2 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -620,14 +620,14 @@ describe('payment-request handler', () => { }); }); - it('payment handler (Iden3PaymentRailsERC20RequestV1)', async () => { + it('payment handler (Iden3PaymentRailsERC20V1)', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ - paymentReqPaymentRailsV1Info + paymentReqPaymentRailsERC20V1Info ]); const payment = createPayment(userDID, issuerDID, [ { nonce: (paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1).nonce, - type: PaymentType.Iden3PaymentRailsV1, + type: PaymentType.Iden3PaymentRailsERC20V1, '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { txId: '0x312312334', @@ -857,7 +857,7 @@ describe('payment-request handler', () => { }); }); - it.skip('payment handler (Iden3PaymentRailsERC20RequestV1, integration test)', async () => { + it.skip('payment handler (Iden3PaymentRailsERC20V1, integration test)', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqPaymentRailsERC20V1Info ]); @@ -868,7 +868,7 @@ describe('payment-request handler', () => { const payment = createPayment(userDID, issuerDID, [ { nonce: data.nonce, - type: PaymentType.Iden3PaymentRailsV1, + type: PaymentType.Iden3PaymentRailsERC20V1, '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { txId: '0x72de0354aee61a9083424a4b852ec80db4f236e31b63345dc3efefc3b197ecca', From 694e5889424c671764b7898a4cdca2ecdd2ee807 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 25 Oct 2024 12:57:54 +0300 Subject: [PATCH 26/51] remove signature from Iden3PaymentRequestCryptoV1 --- src/iden3comm/types/protocol/payment.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index 87d71727..b59c2eac 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -39,7 +39,6 @@ export type Iden3PaymentRequestCryptoV1 = { chainId: string; address: string; currency: SupportedCurrencies; - signature?: string; expiration?: string; }; From c246f727e10ac2c0bb03576f36f448780f80a0ba Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 25 Oct 2024 13:06:54 +0300 Subject: [PATCH 27/51] move opts to type --- .eslintrc.js | 2 +- src/iden3comm/handlers/payment.ts | 51 +++++++++++++++++-------------- src/verifiable/constants.ts | 3 +- tests/handlers/payment.test.ts | 6 ++-- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ba866a70..77fc6a3d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,7 +10,7 @@ module.exports = { ...spellcheckerRule, cspell: { ...cspellConfig, - ignoreWords: ['unmarshal', 'JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw', 'gdwj', 'fwor', 'multichain', "ETHWEI"] + ignoreWords: ['unmarshal', 'JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw', 'gdwj', 'fwor', 'multichain', "ETHWEI", "ETHGWEI"] } } ] diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 5e844eb5..5eabc392 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -57,6 +57,28 @@ export function createPaymentRequest( return request; } +/** + * @beta + * PaymentRailsChainInfo represents chain info for payment rails + */ +export type PaymentRailsChainInfo = { + nonce: bigint; + amount: bigint; + currency: SupportedCurrencies; + chainId: string; + recipient: string; + verifyingContract: string; + expirationDate?: Date; +}; + +/** + * @beta + * ERC20PaymentRailsChainInfo represents chain info for ERC-20 payment rails + */ +export type ERC20PaymentRailsChainInfo = PaymentRailsChainInfo & { + tokenAddress: string; +}; + /** * @beta * createPaymentRailsV1 is a function to create protocol payment message @@ -80,15 +102,7 @@ export async function createPaymentRailsV1( context: string; }[]; description?: string; - chains: { - nonce: bigint; - amount: bigint; - currency: SupportedCurrencies; - chainId: string; - recipient: string; - verifyingContract: string; - expirationDate?: Date; - }[]; + chains: PaymentRailsChainInfo[]; } ]; } @@ -186,16 +200,7 @@ export async function createERC20PaymentRailsV1( context: string; }[]; description?: string; - chains: { - tokenAddress: string; - nonce: bigint; - amount: bigint; - currency: SupportedCurrencies; - chainId: string; - recipient: string; - verifyingContract: string; - expirationDate?: Date; - }[]; + chains: ERC20PaymentRailsChainInfo[]; } ]; } @@ -508,11 +513,11 @@ export class PaymentHandler const selectedPayments = paymentReq.data.filter((p) => { const proofs = Array.isArray(p.proof) ? p.proof : [p.proof]; return ( - proofs.filter( + proofs.find( (p) => p.type === SupportedPaymentProofType.EthereumEip712Signature2021 && p.eip712.domain.chainId === ctx.multichainSelectedChainId - )[0] && + ) && (!p.expirationDate || new Date(p.expirationDate) > new Date()) ); }); @@ -546,11 +551,11 @@ export class PaymentHandler } if ( - selectedPayment.currency !== SupportedCurrencies.ETHWEI && + selectedPayment.currency !== SupportedCurrencies.ETH_WEI && selectedPayment.currency !== SupportedCurrencies.ERC20Token ) { throw new Error( - `failed request. not supported '${selectedPayment.currency}' currency. Only ${SupportedCurrencies.ETHWEI} is supported` + `failed request. not supported '${selectedPayment.currency}' currency. Only ${SupportedCurrencies.ETH_WEI} is supported` ); } diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index a4722145..6df87f91 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -151,7 +151,8 @@ export enum SupportedPaymentProofType { */ export enum SupportedCurrencies { ETH = 'ETH', - ETHWEI = 'ETHWEI', + ETH_WEI = 'ETHWEI', + ETH_GWEI = 'ETHGWEI', MATIC = 'MATIC', ERC20Token = 'ERC20Token' } diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 19ae54d2..f9d88728 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -381,7 +381,7 @@ describe('payment-request handler', () => { ], recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', amount: '100', - currency: SupportedCurrencies.ETHWEI, + currency: SupportedCurrencies.ETH_WEI, expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), nonce: '132', metadata: '0x', @@ -707,7 +707,7 @@ describe('payment-request handler', () => { { nonce: 132n, amount: 100n, - currency: SupportedCurrencies.ETHWEI, + currency: SupportedCurrencies.ETH_WEI, chainId: '80002', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d', @@ -716,7 +716,7 @@ describe('payment-request handler', () => { { nonce: 44n, amount: 10000n, - currency: SupportedCurrencies.ETHWEI, + currency: SupportedCurrencies.ETH_WEI, chainId: '1101', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d', From e2d06ac3146f6d9aac0be9e8a540c0bc88fe4e22 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 25 Oct 2024 13:19:08 +0300 Subject: [PATCH 28/51] remove salt from domain --- src/iden3comm/handlers/payment.ts | 10 ++-------- src/iden3comm/types/protocol/payment.ts | 1 - tests/handlers/payment.test.ts | 8 +++----- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 5eabc392..9a8ca358 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -159,10 +159,7 @@ export async function createPaymentRailsV1( eip712: { types: typeUrl, primaryType: 'Iden3PaymentRailsRequestV1', - domain: { - ...domain, - salt: '' - } + domain } } ] @@ -314,10 +311,7 @@ export async function createERC20PaymentRailsV1( eip712: { types: typeUrl, primaryType: 'Iden3PaymentRailsRequestV1', - domain: { - ...domain, - salt: '' - } + domain } } ] diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index b59c2eac..705fc4ae 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -73,7 +73,6 @@ export type EthereumEip712Signature2021 = { version: string; chainId: string; verifyingContract: string; - salt: string; }; }; }; diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index f9d88728..f1f9ae0c 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -401,8 +401,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d', - salt: '' + verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d' } } } @@ -449,8 +448,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0xCCc1640E846b12578E00F2E17e361c1728cb949D', - salt: '' + verifyingContract: '0xCCc1640E846b12578E00F2E17e361c1728cb949D' } } } @@ -762,7 +760,7 @@ describe('payment-request handler', () => { chains: [ { tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 39n, + nonce: 40n, amount: 30n, currency: SupportedCurrencies.ERC20Token, chainId: '80002', From f4fe3f18669b8b964e632a514edb679fdcfbd176 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 25 Oct 2024 14:39:39 +0300 Subject: [PATCH 29/51] update abi --- tests/handlers/payment.test.ts | 36 +++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index f1f9ae0c..52bda98e 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -147,7 +147,7 @@ describe('payment-request handler', () => { type: 'bytes' } ], - name: 'erc20Payment', + name: 'payERC20Token', outputs: [], stateMutability: 'nonpayable', type: 'function' @@ -216,7 +216,7 @@ describe('payment-request handler', () => { type: 'bytes' } ], - name: 'pay', + name: 'payNativeCurrency', outputs: [], stateMutability: 'payable', type: 'function' @@ -283,7 +283,11 @@ describe('payment-request handler', () => { }; const options = { value: data.amount }; - const txData = await payContract.pay(paymentData, data.proof[0].proofValue, options); + const txData = await payContract.payNativeCurrency( + paymentData, + data.proof[0].proofValue, + options + ); return txData.hash; } else if (data.type == PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1) { const payContract = new Contract( @@ -300,7 +304,7 @@ describe('payment-request handler', () => { metadata: data.metadata }; - const txData = await payContract.erc20Payment(paymentData, data.proof[0].proofValue); + const txData = await payContract.payERC20Token(paymentData, data.proof[0].proofValue); return txData.hash; } else { throw new Error('invalid payment request data type'); @@ -401,7 +405,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d' + verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8' } } } @@ -448,7 +452,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0xCCc1640E846b12578E00F2E17e361c1728cb949D' + verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8' } } } @@ -703,21 +707,21 @@ describe('payment-request handler', () => { description: 'Iden3PaymentRailsRequestV1 payment-request integration test', chains: [ { - nonce: 132n, + nonce: 1n, amount: 100n, currency: SupportedCurrencies.ETH_WEI, chainId: '80002', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d', + verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) }, { - nonce: 44n, + nonce: 1n, amount: 10000n, currency: SupportedCurrencies.ETH_WEI, chainId: '1101', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0xccc1640e846b12578e00f2e17e361c1728cb949d', + verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) } ] @@ -760,22 +764,22 @@ describe('payment-request handler', () => { chains: [ { tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 40n, + nonce: 2n, amount: 30n, currency: SupportedCurrencies.ERC20Token, chainId: '80002', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0xCCc1640E846b12578E00F2E17e361c1728cb949D', + verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) }, { tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 44n, + nonce: 2n, amount: 30n, currency: SupportedCurrencies.ERC20Token, chainId: '1101', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0xCCc1640E846b12578E00F2E17e361c1728cb949D', + verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) } ] @@ -836,7 +840,7 @@ describe('payment-request handler', () => { ]); const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; - data.nonce = '132'; + data.nonce = '1'; const payment = createPayment(userDID, issuerDID, [ { @@ -861,7 +865,7 @@ describe('payment-request handler', () => { ]); const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; - data.nonce = '39'; + data.nonce = '2'; const payment = createPayment(userDID, issuerDID, [ { From dde15780ad3e71aa685b2afd5587b37e2462b916 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 25 Oct 2024 15:27:44 +0300 Subject: [PATCH 30/51] update abi (2) --- tests/handlers/payment.test.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 52bda98e..65138dbe 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -147,7 +147,7 @@ describe('payment-request handler', () => { type: 'bytes' } ], - name: 'payERC20Token', + name: 'payERC20', outputs: [], stateMutability: 'nonpayable', type: 'function' @@ -216,7 +216,7 @@ describe('payment-request handler', () => { type: 'bytes' } ], - name: 'payNativeCurrency', + name: 'pay', outputs: [], stateMutability: 'payable', type: 'function' @@ -283,7 +283,7 @@ describe('payment-request handler', () => { }; const options = { value: data.amount }; - const txData = await payContract.payNativeCurrency( + const txData = await payContract.pay( paymentData, data.proof[0].proofValue, options @@ -304,7 +304,7 @@ describe('payment-request handler', () => { metadata: data.metadata }; - const txData = await payContract.payERC20Token(paymentData, data.proof[0].proofValue); + const txData = await payContract.payERC20(paymentData, data.proof[0].proofValue); return txData.hash; } else { throw new Error('invalid payment request data type'); @@ -405,7 +405,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8' + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a' } } } @@ -452,7 +452,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8' + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a' } } } @@ -712,7 +712,7 @@ describe('payment-request handler', () => { currency: SupportedCurrencies.ETH_WEI, chainId: '80002', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) }, { @@ -721,7 +721,7 @@ describe('payment-request handler', () => { currency: SupportedCurrencies.ETH_WEI, chainId: '1101', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) } ] @@ -769,7 +769,7 @@ describe('payment-request handler', () => { currency: SupportedCurrencies.ERC20Token, chainId: '80002', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) }, { @@ -779,7 +779,7 @@ describe('payment-request handler', () => { currency: SupportedCurrencies.ERC20Token, chainId: '1101', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x92Af66c4f744359a3796665c244193Cd303E6Fd8', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) } ] From 0ee7a7c18986da0e3ae470a1047c2c46ddad5a33 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 25 Oct 2024 15:28:11 +0300 Subject: [PATCH 31/51] format --- tests/handlers/payment.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 65138dbe..ee53b64e 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -283,11 +283,7 @@ describe('payment-request handler', () => { }; const options = { value: data.amount }; - const txData = await payContract.pay( - paymentData, - data.proof[0].proofValue, - options - ); + const txData = await payContract.pay(paymentData, data.proof[0].proofValue, options); return txData.hash; } else if (data.type == PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1) { const payContract = new Contract( From 858b4e7a807d5aed0d824742b806495fb39f9c51 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Sun, 27 Oct 2024 20:27:18 +0200 Subject: [PATCH 32/51] ERC20PermitSupported --- src/iden3comm/handlers/payment.ts | 116 ++++++++++++++++++++- src/iden3comm/types/protocol/payment.ts | 1 + tests/handlers/payment.test.ts | 131 ++++++++++++++++++++++++ 3 files changed, 245 insertions(+), 3 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 9a8ca358..25337ec5 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -24,7 +24,72 @@ import { SupportedCurrencies, SupportedPaymentProofType } from '../../verifiable'; -import { Signer } from 'ethers'; +import { Contract, Signer } from 'ethers'; + +const erc20PermitAbi = [ + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address' + } + ], + name: 'nonces', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'eip712Domain', + outputs: [ + { + internalType: 'bytes1', + name: 'fields', + type: 'bytes1' + }, + { + internalType: 'string', + name: 'name', + type: 'string' + }, + { + internalType: 'string', + name: 'version', + type: 'string' + }, + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256' + }, + { + internalType: 'address', + name: 'verifyingContract', + type: 'address' + }, + { + internalType: 'bytes32', + name: 'salt', + type: 'bytes32' + }, + { + internalType: 'uint256[]', + name: 'extensions', + type: 'uint256[]' + } + ], + stateMutability: 'view', + type: 'function' + } +]; /** * @beta @@ -77,6 +142,7 @@ export type PaymentRailsChainInfo = { */ export type ERC20PaymentRailsChainInfo = PaymentRailsChainInfo & { tokenAddress: string; + ERC20PermitSupported?: boolean; }; /** @@ -209,6 +275,7 @@ export async function createERC20PaymentRailsV1( for (let j = 0; j < opts.payments[i].chains.length; j++) { const { tokenAddress, + ERC20PermitSupported, nonce, amount, currency, @@ -291,9 +358,10 @@ export async function createERC20PaymentRailsV1( dataArr.push({ type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1, '@context': [ - 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1', + 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsERC20RequestV1', 'https://w3id.org/security/suites/eip712sig-2021/v1' ], + ERC20PermitSupported, tokenAddress, recipient, amount: amount.toString(), @@ -326,6 +394,45 @@ export async function createERC20PaymentRailsV1( return createPaymentRequest(sender, receiver, agent, payments); } +export async function getPermitSignature( + signer: Signer, // User who owns the tokens + tokenAddress: string, // EIP-2612 contract address + spender: string, // The contract address that will spend tokens + value: bigint, // Amount of tokens to approve + deadline: number // Timestamp when the permit expires +) { + const erc20PermitContract = new Contract(tokenAddress, erc20PermitAbi, signer); + + const nonce = await erc20PermitContract.nonces(await signer.getAddress()); + const domainData = await erc20PermitContract.eip712Domain(); + const domain = { + name: domainData[1], + version: domainData[2], + chainId: domainData[3], + verifyingContract: tokenAddress + }; + + const types = { + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' } + ] + }; + + const message = { + owner: await signer.getAddress(), + spender: spender, + value: value, + nonce: nonce, + deadline: deadline + }; + + return signer.signTypedData(domain, types, message); +} + /** * @beta * createPayment is a function to create protocol payment message @@ -553,7 +660,10 @@ export class PaymentHandler ); } - if (selectedPayment.type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1) { + if ( + selectedPayment.type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 && + !selectedPayment.ERC20PermitSupported + ) { if (!ctx.erc20TokenApproveHandler) { throw new Error(`please provide erc20TokenApproveHandler in context`); } diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index 705fc4ae..fe9e93a0 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -56,6 +56,7 @@ export type Iden3PaymentRailsRequestV1 = { export type Iden3PaymentRailsERC20RequestV1 = Omit, 'type'> & { tokenAddress: string; + ERC20PermitSupported?: boolean; type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1; }; diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index ee53b64e..879a251f 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -40,6 +40,7 @@ import { createPayment, createPaymentRailsV1, createPaymentRequest, + getPermitSignature, IPaymentHandler, PaymentHandler } from '../../src/iden3comm/handlers/payment'; @@ -152,6 +153,61 @@ describe('payment-request handler', () => { stateMutability: 'nonpayable', type: 'function' }, + { + inputs: [ + { + internalType: 'bytes', + name: 'permitSignature', + type: 'bytes' + }, + { + components: [ + { + internalType: 'address', + name: 'tokenAddress', + type: 'address' + }, + { + internalType: 'address', + name: 'recipient', + type: 'address' + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'expirationDate', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'metadata', + type: 'bytes' + } + ], + internalType: 'struct MCPayment.Iden3PaymentRailsERC20RequestV1', + name: 'paymentData', + type: 'tuple' + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes' + } + ], + name: 'payERC20Permit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, { inputs: [ { @@ -300,6 +356,22 @@ describe('payment-request handler', () => { metadata: data.metadata }; + if (data.ERC20PermitSupported) { + const permitSignature = getPermitSignature( + ethSigner, + data.tokenAddress, + await payContract.getAddress(), + BigInt(data.amount), + new Date(data.expirationDate).getTime() + ); + const txData = await payContract.payERC20Permit( + permitSignature, + paymentData, + data.proof[0].proofValue + ); + return txData.hash; + } + const txData = await payContract.payERC20(paymentData, data.proof[0].proofValue); return txData.hash; } else { @@ -811,6 +883,65 @@ describe('payment-request handler', () => { ); }); + it.skip('payment-request handler (Iden3PaymentRailsERC20RequestV1 Permit, integration test)', async () => { + const rpcProvider = new JsonRpcProvider(RPC_URL); + const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); + const paymentRequest = await createERC20PaymentRailsV1(issuerDID, userDID, agent, ethSigner, { + payments: [ + { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', + chains: [ + { + tokenAddress: '0x2FE40749812FAC39a0F380649eF59E01bccf3a1A', + ERC20PermitSupported: true, + nonce: 2n, + amount: 30n, + currency: SupportedCurrencies.ERC20Token, + chainId: '80002', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x6f742EBA99C3043663f995a7f566e9F012C07925', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + }, + { + tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', + nonce: 2n, + amount: 30n, + currency: SupportedCurrencies.ERC20Token, + chainId: '1101', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + } + ] + } + ] + }); + + const msgBytesRequest = await packageManager.pack( + MediaType.PlainMessage, + byteEncoder.encode(JSON.stringify(paymentRequest)), + {} + ); + const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { + paymentHandler: paymentIntegrationHandlerFunc('', ''), + multichainSelectedChainId: '80002' + }); + if (!agentMessageBytes) { + fail('handlePaymentRequest is not expected null response'); + } + const { unpackedMessage: agentMessage } = await packageManager.unpack(agentMessageBytes); + + expect((agentMessage as BasicMessage).type).to.be.eq( + PROTOCOL_MESSAGE_TYPE.PROPOSAL_MESSAGE_TYPE + ); + }); + it.skip('payment handler (Iden3PaymentRequestCryptoV1, integration test)', async () => { const paymentRequest = createPaymentRequest(issuerDID, userDID, agent, [ paymentReqCryptoV1Info From 2eac6a203176c2f8deeaa8062ec64ec004c1bf17 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Mon, 28 Oct 2024 16:59:45 +0200 Subject: [PATCH 33/51] remove ERC20PermitSupported add add features --- src/iden3comm/handlers/payment.ts | 9 +++++---- src/iden3comm/types/protocol/payment.ts | 3 ++- src/verifiable/constants.ts | 11 ++++++++++- tests/handlers/payment.test.ts | 7 ++++--- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 25337ec5..ff45cea2 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -19,6 +19,7 @@ import { PaymentRequestMessage } from '../types/protocol/payment'; import { + PaymentFeatures, PaymentRequestDataType, PaymentType, SupportedCurrencies, @@ -142,7 +143,7 @@ export type PaymentRailsChainInfo = { */ export type ERC20PaymentRailsChainInfo = PaymentRailsChainInfo & { tokenAddress: string; - ERC20PermitSupported?: boolean; + features?: PaymentFeatures[]; }; /** @@ -275,7 +276,7 @@ export async function createERC20PaymentRailsV1( for (let j = 0; j < opts.payments[i].chains.length; j++) { const { tokenAddress, - ERC20PermitSupported, + features, nonce, amount, currency, @@ -361,7 +362,7 @@ export async function createERC20PaymentRailsV1( 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsERC20RequestV1', 'https://w3id.org/security/suites/eip712sig-2021/v1' ], - ERC20PermitSupported, + features: features || [], tokenAddress, recipient, amount: amount.toString(), @@ -662,7 +663,7 @@ export class PaymentHandler if ( selectedPayment.type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 && - !selectedPayment.ERC20PermitSupported + !selectedPayment.features?.includes(PaymentFeatures.EIP_2612) ) { if (!ctx.erc20TokenApproveHandler) { throw new Error(`please provide erc20TokenApproveHandler in context`); diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index fe9e93a0..fc1750ac 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -1,5 +1,6 @@ import { BasicMessage } from '../'; import { + PaymentFeatures, PaymentRequestDataType, SupportedCurrencies, SupportedPaymentProofType @@ -56,7 +57,7 @@ export type Iden3PaymentRailsRequestV1 = { export type Iden3PaymentRailsERC20RequestV1 = Omit, 'type'> & { tokenAddress: string; - ERC20PermitSupported?: boolean; + features?: PaymentFeatures[]; type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1; }; diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index 6df87f91..833a0853 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -147,7 +147,7 @@ export enum SupportedPaymentProofType { /** * Media types for Payment supported currencies * @beta - * @enum {number} + * @enum {string} */ export enum SupportedCurrencies { ETH = 'ETH', @@ -157,6 +157,15 @@ export enum SupportedCurrencies { ERC20Token = 'ERC20Token' } +/** + * Supported features for payment-request + * @beta + * @enum {string} + */ +export enum PaymentFeatures { + EIP_2612 = 'EIP-2612' +} + /** * DisplayMethodType type for display method * diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 879a251f..d492ff08 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -16,7 +16,8 @@ import { BasicMessage, createProposal, SupportedCurrencies, - SupportedPaymentProofType + SupportedPaymentProofType, + PaymentFeatures } from '../../src'; import { @@ -356,7 +357,7 @@ describe('payment-request handler', () => { metadata: data.metadata }; - if (data.ERC20PermitSupported) { + if (data.features?.includes(PaymentFeatures.EIP_2612)) { const permitSignature = getPermitSignature( ethSigner, data.tokenAddress, @@ -899,7 +900,7 @@ describe('payment-request handler', () => { chains: [ { tokenAddress: '0x2FE40749812FAC39a0F380649eF59E01bccf3a1A', - ERC20PermitSupported: true, + features: [PaymentFeatures.EIP_2612], nonce: 2n, amount: 30n, currency: SupportedCurrencies.ERC20Token, From 6c0fe3c27ab79bd8b324b0ec9ddc35aadee7f291 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 30 Oct 2024 16:40:14 +0200 Subject: [PATCH 34/51] pass nonce to ctx --- src/iden3comm/handlers/payment.ts | 108 +++++------------------------- tests/handlers/payment.test.ts | 21 +++--- 2 files changed, 26 insertions(+), 103 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index ff45cea2..7b164e06 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -289,56 +289,9 @@ export async function createERC20PaymentRailsV1( if (recipient !== (await signer.getAddress())) { throw new Error('recipient is not the signer'); } - const typeUrl = 'https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json'; // todo: REPLACE TO NEW URL + const typeUrl = 'https://schema.iden3.io/core/json/Iden3PaymentRailsERC20RequestV1.json'; const typesFetchResult = await fetch(typeUrl); - let types = await typesFetchResult.json(); - - types = { - EIP712Domain: [ - { - name: 'name', - type: 'string' - }, - { - name: 'version', - type: 'string' - }, - { - name: 'chainId', - type: 'uint256' - }, - { - name: 'verifyingContract', - 'type:': 'address' - } - ], - Iden3PaymentRailsERC20RequestV1: [ - { - name: 'tokenAddress', - type: 'address' - }, - { - name: 'recipient', - type: 'address' - }, - { - name: 'amount', - type: 'uint256' - }, - { - name: 'expirationDate', - type: 'uint256' - }, - { - name: 'nonce', - type: 'uint256' - }, - { - name: 'metadata', - type: 'bytes' - } - ] - }; + const types = await typesFetchResult.json(); delete types.EIP712Domain; const paymentData = { tokenAddress, @@ -504,10 +457,7 @@ export type PaymentRequestMessageHandlerOptions = { paymentHandler: ( data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 ) => Promise; - multichainSelectedChainId?: string; - selectedPaymentType?: - | PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 - | PaymentRequestDataType.Iden3PaymentRailsRequestV1; + nonce?: string; erc20TokenApproveHandler?: (data: Iden3PaymentRailsERC20RequestV1) => Promise; }; @@ -609,38 +559,17 @@ export class PaymentHandler // if multichain request if (Array.isArray(paymentReq.data)) { - if (!ctx.multichainSelectedChainId) { - throw new Error(`failed request. please provide multichainSelectedChainId`); + if (!ctx.nonce) { + throw new Error(`failed request. please provide payment nonce in context`); } - const selectedPayments = paymentReq.data.filter((p) => { - const proofs = Array.isArray(p.proof) ? p.proof : [p.proof]; + const selectedPayment = paymentReq.data.find((p) => { return ( - proofs.find( - (p) => - p.type === SupportedPaymentProofType.EthereumEip712Signature2021 && - p.eip712.domain.chainId === ctx.multichainSelectedChainId - ) && - (!p.expirationDate || new Date(p.expirationDate) > new Date()) + p.nonce === ctx.nonce && (!p.expirationDate || new Date(p.expirationDate) > new Date()) ); }); - if (!selectedPayments.length) { - throw new Error( - `failed request. no payment in request for chain id ${ctx.multichainSelectedChainId}` - ); - } - - let selectedPayment = selectedPayments[0]; - if (ctx.selectedPaymentType) { - const selectedPaymentByType = selectedPayments.find( - (p) => p.type === ctx.selectedPaymentType - ); - if (!selectedPaymentByType) { - throw new Error( - `failed request. no payment in request for chain id ${ctx.multichainSelectedChainId} and type ${ctx.selectedPaymentType}` - ); - } - selectedPayment = selectedPaymentByType; + if (!selectedPayment) { + throw new Error(`failed request. no payment in request for nonce ${ctx.nonce}`); } if ( @@ -652,28 +581,23 @@ export class PaymentHandler ); } - if ( - selectedPayment.currency !== SupportedCurrencies.ETH_WEI && - selectedPayment.currency !== SupportedCurrencies.ERC20Token - ) { - throw new Error( - `failed request. not supported '${selectedPayment.currency}' currency. Only ${SupportedCurrencies.ETH_WEI} is supported` - ); - } - if ( selectedPayment.type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 && !selectedPayment.features?.includes(PaymentFeatures.EIP_2612) ) { if (!ctx.erc20TokenApproveHandler) { - throw new Error(`please provide erc20TokenApproveHandler in context`); + throw new Error( + `please provide erc20TokenApproveHandler in context for ERC-20 payment type` + ); } await ctx.erc20TokenApproveHandler(selectedPayment); } const txId = await ctx.paymentHandler(selectedPayment); - + const proof = Array.isArray(selectedPayment.proof) + ? selectedPayment.proof[0] + : selectedPayment.proof; payments.push({ nonce: selectedPayment.nonce, type: @@ -683,7 +607,7 @@ export class PaymentHandler '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { txId, - chainId: ctx.multichainSelectedChainId + chainId: proof.eip712.domain.chainId } }); diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index d492ff08..d1306474 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -615,7 +615,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentHandlerFuncMock, - multichainSelectedChainId: '80002' + nonce: '132' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -638,8 +638,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentHandlerFuncMock, - multichainSelectedChainId: '80002', - selectedPaymentType: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1, + nonce: '32', erc20TokenApproveHandler: () => Promise.resolve('0x312312334') }); if (!agentMessageBytes) { @@ -785,7 +784,7 @@ describe('payment-request handler', () => { expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) }, { - nonce: 1n, + nonce: 2n, amount: 10000n, currency: SupportedCurrencies.ETH_WEI, chainId: '1101', @@ -805,7 +804,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentIntegrationHandlerFunc('', ''), - multichainSelectedChainId: '80002' + nonce: '1' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -833,7 +832,7 @@ describe('payment-request handler', () => { chains: [ { tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 2n, + nonce: 22n, amount: 30n, currency: SupportedCurrencies.ERC20Token, chainId: '80002', @@ -843,7 +842,7 @@ describe('payment-request handler', () => { }, { tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 2n, + nonce: 23n, amount: 30n, currency: SupportedCurrencies.ERC20Token, chainId: '1101', @@ -863,7 +862,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentIntegrationHandlerFunc('', ''), - multichainSelectedChainId: '80002', + nonce: '22', erc20TokenApproveHandler: async (data: Iden3PaymentRailsERC20RequestV1) => { const token = new Contract(data.tokenAddress, erc20Abi, ethSigner); const txData = await token.approve( @@ -901,7 +900,7 @@ describe('payment-request handler', () => { { tokenAddress: '0x2FE40749812FAC39a0F380649eF59E01bccf3a1A', features: [PaymentFeatures.EIP_2612], - nonce: 2n, + nonce: 33n, amount: 30n, currency: SupportedCurrencies.ERC20Token, chainId: '80002', @@ -911,7 +910,7 @@ describe('payment-request handler', () => { }, { tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 2n, + nonce: 34n, amount: 30n, currency: SupportedCurrencies.ERC20Token, chainId: '1101', @@ -931,7 +930,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentIntegrationHandlerFunc('', ''), - multichainSelectedChainId: '80002' + nonce: '33' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); From 590de0e03c24c2dcb36298ad41b70651271d8273 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 30 Oct 2024 17:23:02 +0200 Subject: [PATCH 35/51] refactor payment handler --- src/iden3comm/handlers/payment.ts | 294 +++++++++--------------- src/iden3comm/types/protocol/payment.ts | 7 +- src/utils/erc-20-permit-sig.ts | 114 +++++++++ src/utils/index.ts | 1 + tests/handlers/payment.test.ts | 19 +- 5 files changed, 239 insertions(+), 196 deletions(-) create mode 100644 src/utils/erc-20-permit-sig.ts diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 7b164e06..5546ef42 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -27,71 +27,6 @@ import { } from '../../verifiable'; import { Contract, Signer } from 'ethers'; -const erc20PermitAbi = [ - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address' - } - ], - name: 'nonces', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'eip712Domain', - outputs: [ - { - internalType: 'bytes1', - name: 'fields', - type: 'bytes1' - }, - { - internalType: 'string', - name: 'name', - type: 'string' - }, - { - internalType: 'string', - name: 'version', - type: 'string' - }, - { - internalType: 'uint256', - name: 'chainId', - type: 'uint256' - }, - { - internalType: 'address', - name: 'verifyingContract', - type: 'address' - }, - { - internalType: 'bytes32', - name: 'salt', - type: 'bytes32' - }, - { - internalType: 'uint256[]', - name: 'extensions', - type: 'uint256[]' - } - ], - stateMutability: 'view', - type: 'function' - } -]; - /** * @beta * createPaymentRequest is a function to create protocol payment-request message @@ -348,45 +283,6 @@ export async function createERC20PaymentRailsV1( return createPaymentRequest(sender, receiver, agent, payments); } -export async function getPermitSignature( - signer: Signer, // User who owns the tokens - tokenAddress: string, // EIP-2612 contract address - spender: string, // The contract address that will spend tokens - value: bigint, // Amount of tokens to approve - deadline: number // Timestamp when the permit expires -) { - const erc20PermitContract = new Contract(tokenAddress, erc20PermitAbi, signer); - - const nonce = await erc20PermitContract.nonces(await signer.getAddress()); - const domainData = await erc20PermitContract.eip712Domain(); - const domain = { - name: domainData[1], - version: domainData[2], - chainId: domainData[3], - verifyingContract: tokenAddress - }; - - const types = { - Permit: [ - { name: 'owner', type: 'address' }, - { name: 'spender', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'deadline', type: 'uint256' } - ] - }; - - const message = { - owner: await signer.getAddress(), - spender: spender, - value: value, - nonce: nonce, - deadline: deadline - }; - - return signer.signTypedData(domain, types, message); -} - /** * @beta * createPayment is a function to create protocol payment message @@ -457,7 +353,10 @@ export type PaymentRequestMessageHandlerOptions = { paymentHandler: ( data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 ) => Promise; - nonce?: string; + /* + selected payment nonce (for Iden3PaymentRequestCryptoV1 type it should be equal to Payment id field) + */ + nonce: string; erc20TokenApproveHandler?: (data: Iden3PaymentRailsERC20RequestV1) => Promise; }; @@ -556,82 +455,37 @@ export class PaymentHandler const payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1 | Iden3PaymentRailsERC20V1)[] = []; for (let i = 0; i < paymentRequest.body.payments.length; i++) { const paymentReq = paymentRequest.body.payments[i]; + const dataArray = Array.isArray(paymentReq.data) ? paymentReq.data : [paymentReq.data]; + const selectedPayment = + dataArray.length === 1 + ? dataArray[0] + : dataArray.find((p) => { + return p.type === PaymentRequestDataType.Iden3PaymentRequestCryptoV1 + ? p.id === ctx.nonce + : p.nonce === ctx.nonce; + }); + + if (!selectedPayment) { + throw new Error(`failed request. no payment in request for nonce ${ctx.nonce}`); + } - // if multichain request - if (Array.isArray(paymentReq.data)) { - if (!ctx.nonce) { - throw new Error(`failed request. please provide payment nonce in context`); - } - const selectedPayment = paymentReq.data.find((p) => { - return ( - p.nonce === ctx.nonce && (!p.expirationDate || new Date(p.expirationDate) > new Date()) + switch (selectedPayment.type) { + case PaymentRequestDataType.Iden3PaymentRequestCryptoV1: + payments.push( + await this.handleIden3PaymentRequestCryptoV1(selectedPayment, ctx.paymentHandler) ); - }); - - if (!selectedPayment) { - throw new Error(`failed request. no payment in request for nonce ${ctx.nonce}`); - } - - if ( - selectedPayment.type !== PaymentRequestDataType.Iden3PaymentRailsRequestV1 && - selectedPayment.type !== PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 - ) { - throw new Error( - `failed request. not supported '${selectedPayment['type']}' payment type ` + break; + case PaymentRequestDataType.Iden3PaymentRailsRequestV1: + payments.push( + await this.handleIden3PaymentRailsRequestV1(selectedPayment, ctx.paymentHandler) ); - } - - if ( - selectedPayment.type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 && - !selectedPayment.features?.includes(PaymentFeatures.EIP_2612) - ) { - if (!ctx.erc20TokenApproveHandler) { - throw new Error( - `please provide erc20TokenApproveHandler in context for ERC-20 payment type` - ); - } - - await ctx.erc20TokenApproveHandler(selectedPayment); - } - - const txId = await ctx.paymentHandler(selectedPayment); - const proof = Array.isArray(selectedPayment.proof) - ? selectedPayment.proof[0] - : selectedPayment.proof; - payments.push({ - nonce: selectedPayment.nonce, - type: - selectedPayment.type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 - ? PaymentType.Iden3PaymentRailsERC20V1 - : PaymentType.Iden3PaymentRailsV1, - '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', - paymentData: { - txId, - chainId: proof.eip712.domain.chainId - } - }); - - continue; - } - - if (paymentReq.data.type !== PaymentRequestDataType.Iden3PaymentRequestCryptoV1) { - throw new Error(`failed request. not supported '${paymentReq.data.type}' payment type `); - } - - if (paymentReq.data.expiration && new Date(paymentReq.data.expiration) < new Date()) { - throw new Error(`failed request. expired request`); + break; + case PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1: + payments.push( + await this.handleIden3PaymentRailsERC20RequestV1(selectedPayment, ctx.paymentHandler) + ); + break; } - - const txId = await ctx.paymentHandler(paymentReq.data); - - payments.push({ - id: paymentReq.data.id, - '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', - type: PaymentType.Iden3PaymentCryptoV1, - paymentData: { - txId - } - }); } const paymentMessage = createPayment(senderDID, receiverDID, payments); @@ -688,10 +542,10 @@ export class PaymentHandler /** * @inheritdoc IPaymentHandler#handlePayment */ - async handlePayment(payment: PaymentMessage, opts: PaymentHandlerOptions) { - if (opts.paymentRequest.from !== payment.to) { + async handlePayment(payment: PaymentMessage, params: PaymentHandlerOptions) { + if (params.paymentRequest.from !== payment.to) { throw new Error( - `sender of the request is not a target of response - expected ${opts.paymentRequest.from}, given ${payment.to}` + `sender of the request is not a target of response - expected ${params.paymentRequest.from}, given ${payment.to}` ); } @@ -708,7 +562,7 @@ export class PaymentHandler | undefined; switch (p.type) { case PaymentType.Iden3PaymentCryptoV1: { - data = opts.paymentRequest.body.payments.find( + data = params.paymentRequest.body.payments.find( (r) => (r.data as Iden3PaymentRequestCryptoV1).id === p.id )?.data as Iden3PaymentRequestCryptoV1; if (!data) { @@ -718,19 +572,18 @@ export class PaymentHandler } case PaymentType.Iden3PaymentRailsV1: case PaymentType.Iden3PaymentRailsERC20V1: { - for (let j = 0; j < opts.paymentRequest.body.payments.length; j++) { - const paymentReq = opts.paymentRequest.body.payments[j]; + for (let j = 0; j < params.paymentRequest.body.payments.length; j++) { + const paymentReq = params.paymentRequest.body.payments[j]; if (Array.isArray(paymentReq.data)) { const selectedPayment = paymentReq.data.find( - (r) => (r as Iden3PaymentRailsRequestV1).nonce === p.nonce - ) as Iden3PaymentRailsRequestV1; + (r) => (r as { nonce: string }).nonce === p.nonce + ); if (selectedPayment) { data = selectedPayment; break; } } } - if (!data) { throw new Error(`can't find payment request for payment nonce ${p.nonce}`); } @@ -739,10 +592,10 @@ export class PaymentHandler default: throw new Error(`failed request. not supported '${p.type}' payment type `); } - if (!opts.paymentValidationHandler) { + if (!params.paymentValidationHandler) { throw new Error(`please provide payment validation handler in options`); } - await opts.paymentValidationHandler(p.paymentData.txId, data); + await params.paymentValidationHandler(p.paymentData.txId, data); } } @@ -759,4 +612,69 @@ export class PaymentHandler ...packerOpts }); } + + private async handleIden3PaymentRequestCryptoV1( + data: Iden3PaymentRequestCryptoV1, + paymentHandler: (data: Iden3PaymentRequestCryptoV1) => Promise + ): Promise { + if (data.expiration && new Date(data.expiration) < new Date()) { + throw new Error(`failed request. expired request`); + } + const txId = await paymentHandler(data); + + return { + id: data.id, + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', + type: PaymentType.Iden3PaymentCryptoV1, + paymentData: { + txId + } + }; + } + + private async handleIden3PaymentRailsRequestV1( + data: Iden3PaymentRailsRequestV1, + paymentHandler: (data: Iden3PaymentRailsRequestV1) => Promise + ): Promise { + const txId = await paymentHandler(data); + const proof = Array.isArray(data.proof) ? data.proof[0] : data.proof; + return { + nonce: data.nonce, + type: PaymentType.Iden3PaymentRailsV1, + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', + paymentData: { + txId, + chainId: proof.eip712.domain.chainId + } + }; + } + + private async handleIden3PaymentRailsERC20RequestV1( + data: Iden3PaymentRailsERC20RequestV1, + paymentHandler: (data: Iden3PaymentRailsERC20RequestV1) => Promise, + approveHandler?: (data: Iden3PaymentRailsERC20RequestV1) => Promise + ): Promise { + if (!data.features?.includes(PaymentFeatures.EIP_2612)) { + if (!approveHandler) { + throw new Error( + `please provide erc20TokenApproveHandler in context for ERC-20 payment type` + ); + } + + await approveHandler(data); + } + + const txId = await paymentHandler(data); + const proof = Array.isArray(data.proof) ? data.proof[0] : data.proof; + return { + nonce: data.nonce, + type: PaymentType.Iden3PaymentRailsERC20V1, + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', + paymentData: { + txId, + chainId: proof.eip712.domain.chainId, + tokenAddress: data.tokenAddress + } + }; + } } diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index fc1750ac..278940be 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -27,7 +27,11 @@ export type PaymentRequestInfo = { }[]; data: | Iden3PaymentRequestCryptoV1 - | (Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1)[]; + | ( + | Iden3PaymentRequestCryptoV1 + | Iden3PaymentRailsRequestV1 + | Iden3PaymentRailsERC20RequestV1 + )[]; description?: string; }; @@ -119,5 +123,6 @@ export type Iden3PaymentRailsERC20V1 = { paymentData: { txId: string; chainId: string; + tokenAddress: string; }; }; diff --git a/src/utils/erc-20-permit-sig.ts b/src/utils/erc-20-permit-sig.ts new file mode 100644 index 00000000..42739bf1 --- /dev/null +++ b/src/utils/erc-20-permit-sig.ts @@ -0,0 +1,114 @@ +import { Contract, Signer } from 'ethers'; + +const erc20PermitAbi = [ + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address' + } + ], + name: 'nonces', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'eip712Domain', + outputs: [ + { + internalType: 'bytes1', + name: 'fields', + type: 'bytes1' + }, + { + internalType: 'string', + name: 'name', + type: 'string' + }, + { + internalType: 'string', + name: 'version', + type: 'string' + }, + { + internalType: 'uint256', + name: 'chainId', + type: 'uint256' + }, + { + internalType: 'address', + name: 'verifyingContract', + type: 'address' + }, + { + internalType: 'bytes32', + name: 'salt', + type: 'bytes32' + }, + { + internalType: 'uint256[]', + name: 'extensions', + type: 'uint256[]' + } + ], + stateMutability: 'view', + type: 'function' + } +]; + +/** + * @beta + * getPermitSignature is a function to create EIP712 Permit signature + * @param {Signer} signer - User who owns the tokens + * @param {string} tokenAddress - EIP-2612 contract address + * @param {string} spender - The contract address that will spend tokens + * @param {bigint} value - Amount of tokens to approve + * @param {number} deadline - Timestamp when the permit expires + * @returns {Promise} + */ +export async function getPermitSignature( + signer: Signer, + tokenAddress: string, + spender: string, + value: bigint, + deadline: number +) { + const erc20PermitContract = new Contract(tokenAddress, erc20PermitAbi, signer); + const nonce = await erc20PermitContract.nonces(await signer.getAddress()); + const domainData = await erc20PermitContract.eip712Domain(); + const domain = { + name: domainData[1], + version: domainData[2], + chainId: domainData[3], + verifyingContract: tokenAddress + }; + + const types = { + Permit: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' } + ] + }; + + const message = { + owner: await signer.getAddress(), + spender: spender, + value: value, + nonce: nonce, + deadline: deadline + }; + + return signer.signTypedData(domain, types, message); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 0905d796..e782d13f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,3 +3,4 @@ export * from './object'; export * from './did-helper'; export * from './message-bus'; export * from './compare-func'; +export * from './erc-20-permit-sig'; diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index d1306474..16ad6477 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -17,7 +17,8 @@ import { createProposal, SupportedCurrencies, SupportedPaymentProofType, - PaymentFeatures + PaymentFeatures, + getPermitSignature } from '../../src'; import { @@ -41,7 +42,6 @@ import { createPayment, createPaymentRailsV1, createPaymentRequest, - getPermitSignature, IPaymentHandler, PaymentHandler } from '../../src/iden3comm/handlers/payment'; @@ -592,7 +592,8 @@ describe('payment-request handler', () => { {} ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { - paymentHandler: paymentHandlerFuncMock + paymentHandler: paymentHandlerFuncMock, + nonce: '12432' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -663,7 +664,8 @@ describe('payment-request handler', () => { {} ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { - paymentHandler: paymentHandlerFuncMock + paymentHandler: paymentHandlerFuncMock, + nonce: '12432' }); expect(agentMessageBytes).to.be.null; }); @@ -701,7 +703,8 @@ describe('payment-request handler', () => { '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { txId: '0x312312334', - chainId: '80002' + chainId: '80002', + tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3' } } ]); @@ -748,7 +751,8 @@ describe('payment-request handler', () => { {} ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { - paymentHandler: paymentIntegrationHandlerFunc('', '') + paymentHandler: paymentIntegrationHandlerFunc('', ''), + nonce: '12432' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -1001,7 +1005,8 @@ describe('payment-request handler', () => { '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { txId: '0x72de0354aee61a9083424a4b852ec80db4f236e31b63345dc3efefc3b197ecca', - chainId: '80002' + chainId: '80002', + tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3' } } ]); From 830f70598ada31a622d71105700840ef1cba3714 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 30 Oct 2024 17:36:36 +0200 Subject: [PATCH 36/51] pass erc20TokenApproveHandler --- src/iden3comm/handlers/payment.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 5546ef42..d2954159 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -482,7 +482,11 @@ export class PaymentHandler break; case PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1: payments.push( - await this.handleIden3PaymentRailsERC20RequestV1(selectedPayment, ctx.paymentHandler) + await this.handleIden3PaymentRailsERC20RequestV1( + selectedPayment, + ctx.paymentHandler, + ctx.erc20TokenApproveHandler + ) ); break; } From a7914aad883653ac58dd62eee13369306a6722d2 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 30 Oct 2024 17:40:48 +0200 Subject: [PATCH 37/51] allow using approveHandler even if 2612 supported --- src/iden3comm/handlers/payment.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index d2954159..b2db3d54 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -658,13 +658,11 @@ export class PaymentHandler paymentHandler: (data: Iden3PaymentRailsERC20RequestV1) => Promise, approveHandler?: (data: Iden3PaymentRailsERC20RequestV1) => Promise ): Promise { - if (!data.features?.includes(PaymentFeatures.EIP_2612)) { - if (!approveHandler) { - throw new Error( - `please provide erc20TokenApproveHandler in context for ERC-20 payment type` - ); - } + if (!data.features?.includes(PaymentFeatures.EIP_2612) && !approveHandler) { + throw new Error(`please provide erc20TokenApproveHandler in context for ERC-20 payment type`); + } + if (approveHandler) { await approveHandler(data); } From 06f286e552f62c522ce5366c980135aea80ede25 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 30 Oct 2024 17:43:05 +0200 Subject: [PATCH 38/51] bump minor version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd6652fd..45b0dc93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@0xpolygonid/js-sdk", - "version": "1.20.3", + "version": "1.22.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@0xpolygonid/js-sdk", - "version": "1.20.3", + "version": "1.22.0", "license": "MIT or Apache-2.0", "dependencies": { "@noble/curves": "^1.4.0", diff --git a/package.json b/package.json index 12efea30..2681f338 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@0xpolygonid/js-sdk", - "version": "1.20.3", + "version": "1.22.0", "description": "SDK to work with Polygon ID", "main": "dist/node/cjs/index.js", "module": "dist/node/esm/index.js", From 470cc8720514099eebaefc5f53fb292d1d2b3847 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 30 Oct 2024 18:03:37 +0200 Subject: [PATCH 39/51] refactor handlePayment and fix response @context --- src/iden3comm/handlers/payment.ts | 68 +++++++++++-------------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index b2db3d54..b9c99757 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -25,7 +25,7 @@ import { SupportedCurrencies, SupportedPaymentProofType } from '../../verifiable'; -import { Contract, Signer } from 'ethers'; +import { Signer } from 'ethers'; /** * @beta @@ -557,49 +557,29 @@ export class PaymentHandler throw new Error(`failed request. empty 'payments' field in body`); } + if (!params.paymentValidationHandler) { + throw new Error(`please provide payment validation handler in options`); + } + for (let i = 0; i < payment.body.payments.length; i++) { const p = payment.body.payments[i]; - let data: - | Iden3PaymentRequestCryptoV1 - | Iden3PaymentRailsRequestV1 - | Iden3PaymentRailsERC20RequestV1 - | undefined; - switch (p.type) { - case PaymentType.Iden3PaymentCryptoV1: { - data = params.paymentRequest.body.payments.find( - (r) => (r.data as Iden3PaymentRequestCryptoV1).id === p.id - )?.data as Iden3PaymentRequestCryptoV1; - if (!data) { - throw new Error(`can't find payment request for payment id ${p.id}`); - } - break; - } - case PaymentType.Iden3PaymentRailsV1: - case PaymentType.Iden3PaymentRailsERC20V1: { - for (let j = 0; j < params.paymentRequest.body.payments.length; j++) { - const paymentReq = params.paymentRequest.body.payments[j]; - if (Array.isArray(paymentReq.data)) { - const selectedPayment = paymentReq.data.find( - (r) => (r as { nonce: string }).nonce === p.nonce - ); - if (selectedPayment) { - data = selectedPayment; - break; - } - } - } - if (!data) { - throw new Error(`can't find payment request for payment nonce ${p.nonce}`); - } - break; - } - default: - throw new Error(`failed request. not supported '${p.type}' payment type `); - } - if (!params.paymentValidationHandler) { - throw new Error(`please provide payment validation handler in options`); + const nonce = p.type === PaymentType.Iden3PaymentCryptoV1 ? p.id : p.nonce; + const requestDataArr = params.paymentRequest.body.payments + .map((r) => (Array.isArray(r.data) ? r.data : [r.data])) + .flat(); + const requestData = requestDataArr.find((r) => + r.type === PaymentRequestDataType.Iden3PaymentRequestCryptoV1 + ? r.id === nonce + : r.nonce === nonce + ); + if (!requestData) { + throw new Error( + `can't find payment request for payment ${ + p.type === PaymentType.Iden3PaymentCryptoV1 ? 'id' : 'nonce' + } ${nonce}` + ); } - await params.paymentValidationHandler(p.paymentData.txId, data); + await params.paymentValidationHandler(p.paymentData.txId, requestData); } } @@ -628,7 +608,7 @@ export class PaymentHandler return { id: data.id, - '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentCryptoV1', type: PaymentType.Iden3PaymentCryptoV1, paymentData: { txId @@ -645,7 +625,7 @@ export class PaymentHandler return { nonce: data.nonce, type: PaymentType.Iden3PaymentRailsV1, - '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsV1', paymentData: { txId, chainId: proof.eip712.domain.chainId @@ -671,7 +651,7 @@ export class PaymentHandler return { nonce: data.nonce, type: PaymentType.Iden3PaymentRailsERC20V1, - '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', + '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsERC20V1', paymentData: { txId, chainId: proof.eip712.domain.chainId, From 4c28e58170e2fe3a01f9ddff7691c505057643f7 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 30 Oct 2024 18:27:45 +0200 Subject: [PATCH 40/51] handle payment expirationDate --- src/iden3comm/handlers/payment.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index b9c99757..c540d515 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -620,6 +620,9 @@ export class PaymentHandler data: Iden3PaymentRailsRequestV1, paymentHandler: (data: Iden3PaymentRailsRequestV1) => Promise ): Promise { + if (data.expirationDate && new Date(data.expirationDate) < new Date()) { + throw new Error(`failed request. expired request`); + } const txId = await paymentHandler(data); const proof = Array.isArray(data.proof) ? data.proof[0] : data.proof; return { @@ -638,6 +641,10 @@ export class PaymentHandler paymentHandler: (data: Iden3PaymentRailsERC20RequestV1) => Promise, approveHandler?: (data: Iden3PaymentRailsERC20RequestV1) => Promise ): Promise { + if (data.expirationDate && new Date(data.expirationDate) < new Date()) { + throw new Error(`failed request. expired request`); + } + if (!data.features?.includes(PaymentFeatures.EIP_2612) && !approveHandler) { throw new Error(`please provide erc20TokenApproveHandler in context for ERC-20 payment type`); } From d432b92f2d6cfc46ec6ab93099edaa8588bf96ed Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Wed, 30 Oct 2024 18:39:06 +0200 Subject: [PATCH 41/51] rm opts from createPaymentRails --- src/iden3comm/handlers/payment.ts | 72 +++++------ tests/handlers/payment.test.ts | 202 +++++++++++++++--------------- 2 files changed, 132 insertions(+), 142 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index c540d515..17128cd6 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -96,26 +96,24 @@ export async function createPaymentRailsV1( receiver: DID, agent: string, signer: Signer, - opts: { - payments: [ - { - credentials: { - type: string; - context: string; - }[]; - description?: string; - chains: PaymentRailsChainInfo[]; - } - ]; - } + payments: [ + { + credentials: { + type: string; + context: string; + }[]; + description?: string; + chains: PaymentRailsChainInfo[]; + } + ] ): Promise { - const payments: PaymentRequestInfo[] = []; - for (let i = 0; i < opts.payments.length; i++) { - const { credentials, description } = opts.payments[i]; + const paymentRequestInfo: PaymentRequestInfo[] = []; + for (let i = 0; i < payments.length; i++) { + const { credentials, description } = payments[i]; const dataArr: Iden3PaymentRailsRequestV1[] = []; - for (let j = 0; j < opts.payments[i].chains.length; j++) { + for (let j = 0; j < payments[i].chains.length; j++) { const { nonce, amount, currency, chainId, recipient, verifyingContract, expirationDate } = - opts.payments[i].chains[j]; + payments[i].chains[j]; if (recipient !== (await signer.getAddress())) { throw new Error('recipient is not the signer'); @@ -167,13 +165,13 @@ export async function createPaymentRailsV1( ] }); } - payments.push({ + paymentRequestInfo.push({ data: dataArr, credentials, description }); } - return createPaymentRequest(sender, receiver, agent, payments); + return createPaymentRequest(sender, receiver, agent, paymentRequestInfo); } /** @@ -191,24 +189,22 @@ export async function createERC20PaymentRailsV1( receiver: DID, agent: string, signer: Signer, - opts: { - payments: [ - { - credentials: { - type: string; - context: string; - }[]; - description?: string; - chains: ERC20PaymentRailsChainInfo[]; - } - ]; - } + payments: [ + { + credentials: { + type: string; + context: string; + }[]; + description?: string; + chains: ERC20PaymentRailsChainInfo[]; + } + ] ): Promise { - const payments: PaymentRequestInfo[] = []; - for (let i = 0; i < opts.payments.length; i++) { - const { credentials, description } = opts.payments[i]; + const paymentRequestInfo: PaymentRequestInfo[] = []; + for (let i = 0; i < payments.length; i++) { + const { credentials, description } = payments[i]; const dataArr: Iden3PaymentRailsERC20RequestV1[] = []; - for (let j = 0; j < opts.payments[i].chains.length; j++) { + for (let j = 0; j < payments[i].chains.length; j++) { const { tokenAddress, features, @@ -219,7 +215,7 @@ export async function createERC20PaymentRailsV1( recipient, verifyingContract, expirationDate - } = opts.payments[i].chains[j]; + } = payments[i].chains[j]; if (recipient !== (await signer.getAddress())) { throw new Error('recipient is not the signer'); @@ -274,13 +270,13 @@ export async function createERC20PaymentRailsV1( ] }); } - payments.push({ + paymentRequestInfo.push({ data: dataArr, credentials, description }); } - return createPaymentRequest(sender, receiver, agent, payments); + return createPaymentRequest(sender, receiver, agent, paymentRequestInfo); } /** diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 16ad6477..7a19e5a3 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -767,39 +767,37 @@ describe('payment-request handler', () => { it.skip('payment-request handler (Iden3PaymentRailsRequestV1, integration test)', async () => { const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); - const paymentRequest = await createPaymentRailsV1(issuerDID, userDID, agent, ethSigner, { - payments: [ - { - credentials: [ - { - type: 'AML', - context: 'http://test.com' - } - ], - description: 'Iden3PaymentRailsRequestV1 payment-request integration test', - chains: [ - { - nonce: 1n, - amount: 100n, - currency: SupportedCurrencies.ETH_WEI, - chainId: '80002', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - }, - { - nonce: 2n, - amount: 10000n, - currency: SupportedCurrencies.ETH_WEI, - chainId: '1101', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - } - ] - } - ] - }); + const paymentRequest = await createPaymentRailsV1(issuerDID, userDID, agent, ethSigner, [ + { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + description: 'Iden3PaymentRailsRequestV1 payment-request integration test', + chains: [ + { + nonce: 1n, + amount: 100n, + currency: SupportedCurrencies.ETH_WEI, + chainId: '80002', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + }, + { + nonce: 2n, + amount: 10000n, + currency: SupportedCurrencies.ETH_WEI, + chainId: '1101', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + } + ] + } + ]); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, @@ -823,41 +821,39 @@ describe('payment-request handler', () => { it.skip('payment-request handler (Iden3PaymentRailsERC20RequestV1, integration test)', async () => { const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); - const paymentRequest = await createERC20PaymentRailsV1(issuerDID, userDID, agent, ethSigner, { - payments: [ - { - credentials: [ - { - type: 'AML', - context: 'http://test.com' - } - ], - description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', - chains: [ - { - tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 22n, - amount: 30n, - currency: SupportedCurrencies.ERC20Token, - chainId: '80002', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - }, - { - tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 23n, - amount: 30n, - currency: SupportedCurrencies.ERC20Token, - chainId: '1101', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - } - ] - } - ] - }); + const paymentRequest = await createERC20PaymentRailsV1(issuerDID, userDID, agent, ethSigner, [ + { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', + chains: [ + { + tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', + nonce: 22n, + amount: 30n, + currency: SupportedCurrencies.ERC20Token, + chainId: '80002', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + }, + { + tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', + nonce: 23n, + amount: 30n, + currency: SupportedCurrencies.ERC20Token, + chainId: '1101', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + } + ] + } + ]); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, @@ -890,42 +886,40 @@ describe('payment-request handler', () => { it.skip('payment-request handler (Iden3PaymentRailsERC20RequestV1 Permit, integration test)', async () => { const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); - const paymentRequest = await createERC20PaymentRailsV1(issuerDID, userDID, agent, ethSigner, { - payments: [ - { - credentials: [ - { - type: 'AML', - context: 'http://test.com' - } - ], - description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', - chains: [ - { - tokenAddress: '0x2FE40749812FAC39a0F380649eF59E01bccf3a1A', - features: [PaymentFeatures.EIP_2612], - nonce: 33n, - amount: 30n, - currency: SupportedCurrencies.ERC20Token, - chainId: '80002', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x6f742EBA99C3043663f995a7f566e9F012C07925', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - }, - { - tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 34n, - amount: 30n, - currency: SupportedCurrencies.ERC20Token, - chainId: '1101', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - } - ] - } - ] - }); + const paymentRequest = await createERC20PaymentRailsV1(issuerDID, userDID, agent, ethSigner, [ + { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', + chains: [ + { + tokenAddress: '0x2FE40749812FAC39a0F380649eF59E01bccf3a1A', + features: [PaymentFeatures.EIP_2612], + nonce: 33n, + amount: 30n, + currency: SupportedCurrencies.ERC20Token, + chainId: '80002', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x6f742EBA99C3043663f995a7f566e9F012C07925', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + }, + { + tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', + nonce: 34n, + amount: 30n, + currency: SupportedCurrencies.ERC20Token, + chainId: '1101', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', + expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) + } + ] + } + ]); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, From 684dceaaecf371f00b182141e6d017edafa5e740 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 31 Oct 2024 17:23:27 +0200 Subject: [PATCH 42/51] proivde config for createPaymentRailsV1 --- src/iden3comm/handlers/payment.ts | 370 +++++++++++------------- src/iden3comm/types/protocol/payment.ts | 15 +- src/verifiable/constants.ts | 3 +- tests/handlers/payment.test.ts | 247 ++++++++-------- 4 files changed, 310 insertions(+), 325 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 17128cd6..85a3383e 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -8,12 +8,14 @@ import { proving } from '@iden3/js-jwz'; import { byteEncoder } from '../../utils'; import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler'; import { + EthereumEip712Signature2021, Iden3PaymentCryptoV1, Iden3PaymentRailsERC20RequestV1, Iden3PaymentRailsERC20V1, Iden3PaymentRailsRequestV1, Iden3PaymentRailsV1, Iden3PaymentRequestCryptoV1, + MultiChainPaymentConfig, PaymentMessage, PaymentRequestInfo, PaymentRequestMessage @@ -58,6 +60,19 @@ export function createPaymentRequest( return request; } +/** + * @beta + * PaymentRailsInfo represents payment info for payment rails + */ +export type PaymentRailsInfo = { + credentials: { + type: string; + context: string; + }[]; + description?: string; + chains: PaymentRailsChainInfo[]; +}; + /** * @beta * PaymentRailsChainInfo represents chain info for payment rails @@ -65,220 +80,15 @@ export function createPaymentRequest( export type PaymentRailsChainInfo = { nonce: bigint; amount: bigint; - currency: SupportedCurrencies; + currency: SupportedCurrencies | string; chainId: string; - recipient: string; - verifyingContract: string; expirationDate?: Date; -}; - -/** - * @beta - * ERC20PaymentRailsChainInfo represents chain info for ERC-20 payment rails - */ -export type ERC20PaymentRailsChainInfo = PaymentRailsChainInfo & { - tokenAddress: string; features?: PaymentFeatures[]; + type: + | PaymentRequestDataType.Iden3PaymentRailsRequestV1 + | PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1; }; -/** - * @beta - * createPaymentRailsV1 is a function to create protocol payment message - * @param {DID} sender - sender did - * @param {DID} receiver - receiver did - * @param {Signer} signer - receiver did - * @param {string} agent - agent URL - * @param opts - payment options - * @returns {Promise} - */ -export async function createPaymentRailsV1( - sender: DID, - receiver: DID, - agent: string, - signer: Signer, - payments: [ - { - credentials: { - type: string; - context: string; - }[]; - description?: string; - chains: PaymentRailsChainInfo[]; - } - ] -): Promise { - const paymentRequestInfo: PaymentRequestInfo[] = []; - for (let i = 0; i < payments.length; i++) { - const { credentials, description } = payments[i]; - const dataArr: Iden3PaymentRailsRequestV1[] = []; - for (let j = 0; j < payments[i].chains.length; j++) { - const { nonce, amount, currency, chainId, recipient, verifyingContract, expirationDate } = - payments[i].chains[j]; - - if (recipient !== (await signer.getAddress())) { - throw new Error('recipient is not the signer'); - } - const typeUrl = 'https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json'; - const typesFetchResult = await fetch(typeUrl); - const types = await typesFetchResult.json(); - delete types.EIP712Domain; - const paymentData = { - recipient, - amount, - expirationDate: expirationDate?.getTime() ?? 0, - nonce, - metadata: '0x' - }; - - const domain = { - name: 'MCPayment', - version: '1.0.0', - chainId, - verifyingContract - }; - const signature = await signer.signTypedData(domain, types, paymentData); - dataArr.push({ - type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, - '@context': [ - 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1', - 'https://w3id.org/security/suites/eip712sig-2021/v1' - ], - recipient, - amount: amount.toString(), - currency, - expirationDate: expirationDate?.toISOString() ?? '', - nonce: nonce.toString(), - metadata: '0x', - proof: [ - { - type: SupportedPaymentProofType.EthereumEip712Signature2021, - proofPurpose: 'assertionMethod', - proofValue: signature, - verificationMethod: `did:pkh:eip155:${chainId}:${recipient}#blockchainAccountId`, - created: new Date().toISOString(), - eip712: { - types: typeUrl, - primaryType: 'Iden3PaymentRailsRequestV1', - domain - } - } - ] - }); - } - paymentRequestInfo.push({ - data: dataArr, - credentials, - description - }); - } - return createPaymentRequest(sender, receiver, agent, paymentRequestInfo); -} - -/** - * @beta - * createPaymentRailsV1 is a function to create protocol payment message - * @param {DID} sender - sender did - * @param {DID} receiver - receiver did - * @param {Signer} signer - receiver did - * @param {string} agent - agent URL - * @param opts - payment options - * @returns {Promise} - */ -export async function createERC20PaymentRailsV1( - sender: DID, - receiver: DID, - agent: string, - signer: Signer, - payments: [ - { - credentials: { - type: string; - context: string; - }[]; - description?: string; - chains: ERC20PaymentRailsChainInfo[]; - } - ] -): Promise { - const paymentRequestInfo: PaymentRequestInfo[] = []; - for (let i = 0; i < payments.length; i++) { - const { credentials, description } = payments[i]; - const dataArr: Iden3PaymentRailsERC20RequestV1[] = []; - for (let j = 0; j < payments[i].chains.length; j++) { - const { - tokenAddress, - features, - nonce, - amount, - currency, - chainId, - recipient, - verifyingContract, - expirationDate - } = payments[i].chains[j]; - - if (recipient !== (await signer.getAddress())) { - throw new Error('recipient is not the signer'); - } - const typeUrl = 'https://schema.iden3.io/core/json/Iden3PaymentRailsERC20RequestV1.json'; - const typesFetchResult = await fetch(typeUrl); - const types = await typesFetchResult.json(); - delete types.EIP712Domain; - const paymentData = { - tokenAddress, - recipient, - amount, - expirationDate: expirationDate?.getTime() ?? 0, - nonce, - metadata: '0x' - }; - - const domain = { - name: 'MCPayment', - version: '1.0.0', - chainId, - verifyingContract - }; - const signature = await signer.signTypedData(domain, types, paymentData); - dataArr.push({ - type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1, - '@context': [ - 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsERC20RequestV1', - 'https://w3id.org/security/suites/eip712sig-2021/v1' - ], - features: features || [], - tokenAddress, - recipient, - amount: amount.toString(), - currency, - expirationDate: expirationDate?.toISOString() ?? '', - nonce: nonce.toString(), - metadata: '0x', - proof: [ - { - type: SupportedPaymentProofType.EthereumEip712Signature2021, - proofPurpose: 'assertionMethod', - proofValue: signature, - verificationMethod: `did:pkh:eip155:${chainId}:${recipient}#blockchainAccountId`, - created: new Date().toISOString(), - eip712: { - types: typeUrl, - primaryType: 'Iden3PaymentRailsRequestV1', - domain - } - } - ] - }); - } - paymentRequestInfo.push({ - data: dataArr, - credentials, - description - }); - } - return createPaymentRequest(sender, receiver, agent, paymentRequestInfo); -} - /** * @beta * createPayment is a function to create protocol payment message @@ -342,6 +152,24 @@ export interface IPaymentHandler { * @returns `Promise` */ handlePayment(payment: PaymentMessage, opts: PaymentHandlerOptions): Promise; + + /** + * @beta + * createPaymentRailsV1 is a function to create protocol payment message + * @param {DID} sender - sender did + * @param {DID} receiver - receiver did + * @param {string} agent - agent URL + * @param {Signer} signer - receiver did + * @param payments - payment options + * @returns {Promise} + */ + createPaymentRailsV1( + sender: DID, + receiver: DID, + agent: string, + signer: Signer, + payments: PaymentRailsInfo[] + ): Promise; } /** @beta PaymentRequestMessageHandlerOptions represents payment-request handler options */ @@ -368,6 +196,7 @@ export type PaymentHandlerOptions = { /** @beta PaymentHandlerParams represents payment handler params */ export type PaymentHandlerParams = { packerParams: PackerParams; + multiChainPaymentConfig?: MultiChainPaymentConfig[]; }; /** @@ -579,6 +408,129 @@ export class PaymentHandler } } + /** + * @inheritdoc createERC20PaymentRailsV1#createPaymentRailsV1 + */ + async createPaymentRailsV1( + sender: DID, + receiver: DID, + agent: string, + signer: Signer, + payments: PaymentRailsInfo[] + ): Promise { + const paymentRequestInfo: PaymentRequestInfo[] = []; + for (let i = 0; i < payments.length; i++) { + const { credentials, description } = payments[i]; + const dataArr: (Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1)[] = []; + for (let j = 0; j < payments[i].chains.length; j++) { + const { features, nonce, amount, currency, chainId, expirationDate, type } = + payments[i].chains[j]; + + const multiChainConfig = this._params.multiChainPaymentConfig?.find( + (c) => c.chainId === chainId + ); + if (!multiChainConfig) { + throw new Error(`failed request. no config for chain ${chainId}`); + } + const { recipient, paymentContract, erc20TokenAddressArr } = multiChainConfig; + if (recipient !== (await signer.getAddress())) { + throw new Error('recipient is not the signer'); + } + + const tokenAddress = erc20TokenAddressArr.find((t) => t.symbol === currency)?.address; + if (type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 && !tokenAddress) { + throw new Error(`failed request. no token address for currency ${currency}`); + } + const expiration = expirationDate + ? expirationDate.getTime() + : new Date(new Date().setHours(new Date().getHours() + 1)).getTime(); + const typeUrl = `https://schema.iden3.io/core/json/${type}.json`; + const typesFetchResult = await fetch(typeUrl); + const types = await typesFetchResult.json(); + delete types.EIP712Domain; + const paymentData = + type === PaymentRequestDataType.Iden3PaymentRailsRequestV1 + ? { + recipient, + amount, + expirationDate: expiration, + nonce, + metadata: '0x' + } + : { + tokenAddress, + recipient, + amount, + expirationDate: expiration, + nonce, + metadata: '0x' + }; + + const domain = { + name: 'MCPayment', + version: '1.0.0', + chainId, + verifyingContract: paymentContract + }; + const signature = await signer.signTypedData(domain, types, paymentData); + const proof: EthereumEip712Signature2021[] = [ + { + type: SupportedPaymentProofType.EthereumEip712Signature2021, + proofPurpose: 'assertionMethod', + proofValue: signature, + verificationMethod: `did:pkh:eip155:${chainId}:${recipient}#blockchainAccountId`, + created: new Date().toISOString(), + eip712: { + types: typeUrl, + primaryType: 'Iden3PaymentRailsRequestV1', + domain + } + } + ]; + + dataArr.push( + type === PaymentRequestDataType.Iden3PaymentRailsRequestV1 + ? { + type, + '@context': [ + `https://schema.iden3.io/core/jsonld/payment.jsonld#${type}`, + 'https://w3id.org/security/suites/eip712sig-2021/v1' + ], + recipient, + amount: amount.toString(), + currency, + expirationDate: new Date(expiration).toISOString(), + nonce: nonce.toString(), + metadata: '0x', + proof + } + : { + type, + '@context': [ + `https://schema.iden3.io/core/jsonld/payment.jsonld#${type}`, + 'https://w3id.org/security/suites/eip712sig-2021/v1' + ], + features: features || [], + tokenAddress: tokenAddress || '', + recipient, + amount: amount.toString(), + currency, + expirationDate: new Date(expiration).toISOString(), + nonce: nonce.toString(), + metadata: '0x', + proof + } + ); + } + paymentRequestInfo.push({ + data: dataArr, + credentials, + description + }); + } + return createPaymentRequest(sender, receiver, agent, paymentRequestInfo); + } + private async packMessage(message: BasicMessage, senderDID: DID): Promise { const responseEncoded = byteEncoder.encode(JSON.stringify(message)); const packerOpts = diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index 278940be..47e59186 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -43,7 +43,7 @@ export type Iden3PaymentRequestCryptoV1 = { id: string; chainId: string; address: string; - currency: SupportedCurrencies; + currency: SupportedCurrencies | string; expiration?: string; }; @@ -52,7 +52,7 @@ export type Iden3PaymentRailsRequestV1 = { '@context': string | (string | object)[]; recipient: string; amount: string; - currency: SupportedCurrencies; + currency: SupportedCurrencies | string; expirationDate: string; nonce: string; metadata: string; @@ -126,3 +126,14 @@ export type Iden3PaymentRailsERC20V1 = { tokenAddress: string; }; }; + +/** @beta MultiChainPaymentConfig is struct that represents payments contracts information for different chains */ +export type MultiChainPaymentConfig = { + chainId: string; + paymentContract: string; + recipient: string; + erc20TokenAddressArr: { + symbol: string; + address: string; + }[]; +}; diff --git a/src/verifiable/constants.ts b/src/verifiable/constants.ts index 833a0853..3efd35ab 100644 --- a/src/verifiable/constants.ts +++ b/src/verifiable/constants.ts @@ -154,7 +154,8 @@ export enum SupportedCurrencies { ETH_WEI = 'ETHWEI', ETH_GWEI = 'ETHGWEI', MATIC = 'MATIC', - ERC20Token = 'ERC20Token' + USDT = 'USDT', + USDC = 'USDC' } /** diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 7a19e5a3..12256b32 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -38,9 +38,7 @@ import path from 'path'; import { MediaType, PROTOCOL_MESSAGE_TYPE } from '../../src/iden3comm/constants'; import { DID } from '@iden3/js-iden3-core'; import { - createERC20PaymentRailsV1, createPayment, - createPaymentRailsV1, createPaymentRequest, IPaymentHandler, PaymentHandler @@ -474,7 +472,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a' + verifyingContract: '0x380dd90852d3Fe75B4f08D0c47416D6c4E0dC774' } } } @@ -501,7 +499,7 @@ describe('payment-request handler', () => { tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', amount: '1', - currency: SupportedCurrencies.ERC20Token, + currency: 'TST', expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), nonce: '32', metadata: '0x', @@ -521,7 +519,7 @@ describe('payment-request handler', () => { name: 'MCPayment', version: '1.0.0', chainId: '80002', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a' + verifyingContract: '0x380dd90852d3Fe75B4f08D0c47416D6c4E0dC774' } } } @@ -562,7 +560,28 @@ describe('payment-request handler', () => { paymentHandler = new PaymentHandler(packageMgr, { packerParams: { mediaType: MediaType.PlainMessage - } + }, + multiChainPaymentConfig: [ + { + chainId: '80002', + paymentContract: '0x380dd90852d3Fe75B4f08D0c47416D6c4E0dC774', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + erc20TokenAddressArr: [ + { symbol: 'TST', address: '0x2FE40749812FAC39a0F380649eF59E01bccf3a1A' } + ] + }, + { + chainId: '1101', + paymentContract: '0x380dd90852d3Fe75B4f08D0c47416D6c4E0dC774', + recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + erc20TokenAddressArr: [ + { + symbol: SupportedCurrencies.USDT, + address: '0x1e4a5963abfd975d8c9021ce480b42188849d41d' + } + ] + } + ] }); const userIdentity = await createIdentity(idWallet, { @@ -767,37 +786,39 @@ describe('payment-request handler', () => { it.skip('payment-request handler (Iden3PaymentRailsRequestV1, integration test)', async () => { const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); - const paymentRequest = await createPaymentRailsV1(issuerDID, userDID, agent, ethSigner, [ - { - credentials: [ - { - type: 'AML', - context: 'http://test.com' - } - ], - description: 'Iden3PaymentRailsRequestV1 payment-request integration test', - chains: [ - { - nonce: 1n, - amount: 100n, - currency: SupportedCurrencies.ETH_WEI, - chainId: '80002', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - }, - { - nonce: 2n, - amount: 10000n, - currency: SupportedCurrencies.ETH_WEI, - chainId: '1101', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - } - ] - } - ]); + const paymentRequest = await paymentHandler.createPaymentRailsV1( + issuerDID, + userDID, + agent, + ethSigner, + [ + { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + description: 'Iden3PaymentRailsRequestV1 payment-request integration test', + chains: [ + { + nonce: 10002n, + amount: 100n, + currency: SupportedCurrencies.ETH_WEI, + chainId: '80002', + type: PaymentRequestDataType.Iden3PaymentRailsRequestV1 + }, + { + nonce: 10001112n, + amount: 10000n, + currency: SupportedCurrencies.ETH_WEI, + chainId: '1101', + type: PaymentRequestDataType.Iden3PaymentRailsRequestV1 + } + ] + } + ] + ); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, @@ -806,7 +827,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentIntegrationHandlerFunc('', ''), - nonce: '1' + nonce: '10002' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -821,39 +842,39 @@ describe('payment-request handler', () => { it.skip('payment-request handler (Iden3PaymentRailsERC20RequestV1, integration test)', async () => { const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); - const paymentRequest = await createERC20PaymentRailsV1(issuerDID, userDID, agent, ethSigner, [ - { - credentials: [ - { - type: 'AML', - context: 'http://test.com' - } - ], - description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', - chains: [ - { - tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 22n, - amount: 30n, - currency: SupportedCurrencies.ERC20Token, - chainId: '80002', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - }, - { - tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 23n, - amount: 30n, - currency: SupportedCurrencies.ERC20Token, - chainId: '1101', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - } - ] - } - ]); + const paymentRequest = await paymentHandler.createPaymentRailsV1( + issuerDID, + userDID, + agent, + ethSigner, + [ + { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', + chains: [ + { + nonce: 22003n, + amount: 30n, + currency: 'TST', + chainId: '80002', + type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 + }, + { + nonce: 220011122n, + amount: 30n, + currency: SupportedCurrencies.USDT, + chainId: '1101', + type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 + } + ] + } + ] + ); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, @@ -862,14 +883,14 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentIntegrationHandlerFunc('', ''), - nonce: '22', + nonce: '22003', erc20TokenApproveHandler: async (data: Iden3PaymentRailsERC20RequestV1) => { const token = new Contract(data.tokenAddress, erc20Abi, ethSigner); const txData = await token.approve( data.proof[0].eip712.domain.verifyingContract, data.amount ); - await txData.wait(3); + await txData.wait(1); return txData.hash; } }); @@ -886,40 +907,40 @@ describe('payment-request handler', () => { it.skip('payment-request handler (Iden3PaymentRailsERC20RequestV1 Permit, integration test)', async () => { const rpcProvider = new JsonRpcProvider(RPC_URL); const ethSigner = new ethers.Wallet(WALLET_KEY, rpcProvider); - const paymentRequest = await createERC20PaymentRailsV1(issuerDID, userDID, agent, ethSigner, [ - { - credentials: [ - { - type: 'AML', - context: 'http://test.com' - } - ], - description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', - chains: [ - { - tokenAddress: '0x2FE40749812FAC39a0F380649eF59E01bccf3a1A', - features: [PaymentFeatures.EIP_2612], - nonce: 33n, - amount: 30n, - currency: SupportedCurrencies.ERC20Token, - chainId: '80002', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x6f742EBA99C3043663f995a7f566e9F012C07925', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - }, - { - tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', - nonce: 34n, - amount: 30n, - currency: SupportedCurrencies.ERC20Token, - chainId: '1101', - recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - verifyingContract: '0x40F63e736146ACC1D30844093d41cbFcF515559a', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)) - } - ] - } - ]); + const paymentRequest = await paymentHandler.createPaymentRailsV1( + issuerDID, + userDID, + agent, + ethSigner, + [ + { + credentials: [ + { + type: 'AML', + context: 'http://test.com' + } + ], + description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', + chains: [ + { + features: [PaymentFeatures.EIP_2612], + nonce: 330003n, + amount: 30n, + currency: 'TST', + chainId: '80002', + type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 + }, + { + nonce: 330001122n, + amount: 30n, + currency: SupportedCurrencies.USDT, + chainId: '1101', + type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 + } + ] + } + ] + ); const msgBytesRequest = await packageManager.pack( MediaType.PlainMessage, @@ -928,7 +949,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentIntegrationHandlerFunc('', ''), - nonce: '33' + nonce: '330003' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -965,7 +986,7 @@ describe('payment-request handler', () => { ]); const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; - data.nonce = '1'; + data.nonce = '10001'; const payment = createPayment(userDID, issuerDID, [ { @@ -973,7 +994,7 @@ describe('payment-request handler', () => { type: PaymentType.Iden3PaymentRailsV1, '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { - txId: '0xea5d9f4396d403b3e88b13fba4f2e5e12347488a76f08544c6bc1efc1961de4c', + txId: '0x59b54f1a3a53d48d891c750db7c300acf8e0dc6bb7daccbf42ce8f62d2957bae', chainId: '80002' } } @@ -990,7 +1011,7 @@ describe('payment-request handler', () => { ]); const data = paymentRequest.body.payments[0].data[0] as Iden3PaymentRailsRequestV1; - data.nonce = '2'; + data.nonce = '330001'; const payment = createPayment(userDID, issuerDID, [ { @@ -998,9 +1019,9 @@ describe('payment-request handler', () => { type: PaymentType.Iden3PaymentRailsERC20V1, '@context': 'https://schema.iden3.io/core/jsonld/payment.jsonld', paymentData: { - txId: '0x72de0354aee61a9083424a4b852ec80db4f236e31b63345dc3efefc3b197ecca', + txId: '0xfd270399a07a7dfc9e184699e8ff8c8b2c59327f27841401b28dc910307d4cb0', chainId: '80002', - tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3' + tokenAddress: '0x2FE40749812FAC39a0F380649eF59E01bccf3a1A' } } ]); From 9e36bc024550af54cdfb8ce728b75e003ef5463a Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 1 Nov 2024 13:14:09 +0200 Subject: [PATCH 43/51] move erc20-permit-sig to storage/blockchain --- src/storage/blockchain/abi/ERC20Permit.json | 1 + .../blockchain/erc20-permit-sig.ts} | 68 +------------------ src/storage/blockchain/index.ts | 1 + src/utils/index.ts | 1 - 4 files changed, 4 insertions(+), 67 deletions(-) create mode 100644 src/storage/blockchain/abi/ERC20Permit.json rename src/{utils/erc-20-permit-sig.ts => storage/blockchain/erc20-permit-sig.ts} (51%) diff --git a/src/storage/blockchain/abi/ERC20Permit.json b/src/storage/blockchain/abi/ERC20Permit.json new file mode 100644 index 00000000..1e28f8f9 --- /dev/null +++ b/src/storage/blockchain/abi/ERC20Permit.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"ECDSAInvalidSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"name":"ECDSAInvalidSignatureLength","type":"error"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"ECDSAInvalidSignatureS","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC20InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC20InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC20InvalidSender","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"name":"ERC20InvalidSpender","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ERC2612ExpiredSignature","type":"error"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"ERC2612InvalidSigner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"currentNonce","type":"uint256"}],"name":"InvalidAccountNonce","type":"error"},{"inputs":[],"name":"InvalidShortString","type":"error"},{"inputs":[{"internalType":"string","name":"str","type":"string"}],"name":"StringTooLong","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[],"name":"EIP712DomainChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/src/utils/erc-20-permit-sig.ts b/src/storage/blockchain/erc20-permit-sig.ts similarity index 51% rename from src/utils/erc-20-permit-sig.ts rename to src/storage/blockchain/erc20-permit-sig.ts index 42739bf1..4d89d659 100644 --- a/src/utils/erc-20-permit-sig.ts +++ b/src/storage/blockchain/erc20-permit-sig.ts @@ -1,69 +1,6 @@ import { Contract, Signer } from 'ethers'; -const erc20PermitAbi = [ - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address' - } - ], - name: 'nonces', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256' - } - ], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'eip712Domain', - outputs: [ - { - internalType: 'bytes1', - name: 'fields', - type: 'bytes1' - }, - { - internalType: 'string', - name: 'name', - type: 'string' - }, - { - internalType: 'string', - name: 'version', - type: 'string' - }, - { - internalType: 'uint256', - name: 'chainId', - type: 'uint256' - }, - { - internalType: 'address', - name: 'verifyingContract', - type: 'address' - }, - { - internalType: 'bytes32', - name: 'salt', - type: 'bytes32' - }, - { - internalType: 'uint256[]', - name: 'extensions', - type: 'uint256[]' - } - ], - stateMutability: 'view', - type: 'function' - } -]; +import abi from './abi/ERC20Permit.json'; /** * @beta @@ -82,11 +19,10 @@ export async function getPermitSignature( value: bigint, deadline: number ) { - const erc20PermitContract = new Contract(tokenAddress, erc20PermitAbi, signer); + const erc20PermitContract = new Contract(tokenAddress, abi, signer); const nonce = await erc20PermitContract.nonces(await signer.getAddress()); const domainData = await erc20PermitContract.eip712Domain(); const domain = { - name: domainData[1], version: domainData[2], chainId: domainData[3], verifyingContract: tokenAddress diff --git a/src/storage/blockchain/index.ts b/src/storage/blockchain/index.ts index 7f4dcae5..01cca339 100644 --- a/src/storage/blockchain/index.ts +++ b/src/storage/blockchain/index.ts @@ -1,3 +1,4 @@ export * from './state'; export * from './onchain-zkp-verifier'; export * from './onchain-revocation'; +export * from './erc20-permit-sig'; diff --git a/src/utils/index.ts b/src/utils/index.ts index e782d13f..0905d796 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,4 +3,3 @@ export * from './object'; export * from './did-helper'; export * from './message-bus'; export * from './compare-func'; -export * from './erc-20-permit-sig'; From 119ad862add6e75a60f753df54649b706c13f65e Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 1 Nov 2024 15:14:30 +0200 Subject: [PATCH 44/51] resolve comments --- src/iden3comm/handlers/payment.ts | 46 +++++++++++-------------- src/iden3comm/types/protocol/payment.ts | 20 ++++++++++- tests/handlers/payment.test.ts | 5 +-- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 85a3383e..203db4cf 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -18,7 +18,9 @@ import { MultiChainPaymentConfig, PaymentMessage, PaymentRequestInfo, - PaymentRequestMessage + PaymentRequestMessage, + PaymentRequestTypeUnion, + PaymentTypeUnion } from '../types/protocol/payment'; import { PaymentFeatures, @@ -82,7 +84,7 @@ export type PaymentRailsChainInfo = { amount: bigint; currency: SupportedCurrencies | string; chainId: string; - expirationDate?: Date; + expirationDate?: string; features?: PaymentFeatures[]; type: | PaymentRequestDataType.Iden3PaymentRailsRequestV1 @@ -100,7 +102,7 @@ export type PaymentRailsChainInfo = { export function createPayment( sender: DID, receiver: DID, - payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1 | Iden3PaymentRailsERC20V1)[] + payments: PaymentTypeUnion[] ): PaymentMessage { const uuidv4 = uuid.v4(); const request: PaymentMessage = { @@ -174,9 +176,7 @@ export interface IPaymentHandler { /** @beta PaymentRequestMessageHandlerOptions represents payment-request handler options */ export type PaymentRequestMessageHandlerOptions = { - paymentHandler: ( - data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 - ) => Promise; + paymentHandler: (data: PaymentRequestTypeUnion) => Promise; /* selected payment nonce (for Iden3PaymentRequestCryptoV1 type it should be equal to Payment id field) */ @@ -187,10 +187,7 @@ export type PaymentRequestMessageHandlerOptions = { /** @beta PaymentHandlerOptions represents payment handler options */ export type PaymentHandlerOptions = { paymentRequest: PaymentRequestMessage; - paymentValidationHandler: ( - txId: string, - data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 - ) => Promise; + paymentValidationHandler: (txId: string, data: PaymentRequestTypeUnion) => Promise; }; /** @beta PaymentHandlerParams represents payment handler params */ @@ -277,18 +274,16 @@ export class PaymentHandler const senderDID = DID.parse(paymentRequest.to); const receiverDID = DID.parse(paymentRequest.from); - const payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1 | Iden3PaymentRailsERC20V1)[] = []; + const payments: PaymentTypeUnion[] = []; for (let i = 0; i < paymentRequest.body.payments.length; i++) { - const paymentReq = paymentRequest.body.payments[i]; - const dataArray = Array.isArray(paymentReq.data) ? paymentReq.data : [paymentReq.data]; - const selectedPayment = - dataArray.length === 1 - ? dataArray[0] - : dataArray.find((p) => { - return p.type === PaymentRequestDataType.Iden3PaymentRequestCryptoV1 - ? p.id === ctx.nonce - : p.nonce === ctx.nonce; - }); + const { data } = paymentRequest.body.payments[i]; + const selectedPayment = Array.isArray(data) + ? data.find((p) => { + return p.type === PaymentRequestDataType.Iden3PaymentRequestCryptoV1 + ? p.id === ctx.nonce + : p.nonce === ctx.nonce; + }) + : data; if (!selectedPayment) { throw new Error(`failed request. no payment in request for nonce ${ctx.nonce}`); @@ -441,9 +436,8 @@ export class PaymentHandler if (type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 && !tokenAddress) { throw new Error(`failed request. no token address for currency ${currency}`); } - const expiration = expirationDate - ? expirationDate.getTime() - : new Date(new Date().setHours(new Date().getHours() + 1)).getTime(); + const expiration = + expirationDate ?? new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(); const typeUrl = `https://schema.iden3.io/core/json/${type}.json`; const typesFetchResult = await fetch(typeUrl); const types = await typesFetchResult.json(); @@ -499,7 +493,7 @@ export class PaymentHandler recipient, amount: amount.toString(), currency, - expirationDate: new Date(expiration).toISOString(), + expirationDate: expiration, nonce: nonce.toString(), metadata: '0x', proof @@ -515,7 +509,7 @@ export class PaymentHandler recipient, amount: amount.toString(), currency, - expirationDate: new Date(expiration).toISOString(), + expirationDate: expiration, nonce: nonce.toString(), metadata: '0x', proof diff --git a/src/iden3comm/types/protocol/payment.ts b/src/iden3comm/types/protocol/payment.ts index 47e59186..accc7d44 100644 --- a/src/iden3comm/types/protocol/payment.ts +++ b/src/iden3comm/types/protocol/payment.ts @@ -91,7 +91,7 @@ export type PaymentMessage = BasicMessage & { /** @beta PaymentMessageBody is struct the represents body for payment message */ export type PaymentMessageBody = { - payments: (Iden3PaymentCryptoV1 | Iden3PaymentRailsV1 | Iden3PaymentRailsERC20V1)[]; + payments: PaymentTypeUnion[]; }; /** @beta Iden3PaymentCryptoV1 is struct the represents payment info for payment */ @@ -137,3 +137,21 @@ export type MultiChainPaymentConfig = { address: string; }[]; }; + +/** + * @beta + * PaymentRequestTypeUnion is a type of supported payment request types + */ +export type PaymentRequestTypeUnion = + | Iden3PaymentRequestCryptoV1 + | Iden3PaymentRailsRequestV1 + | Iden3PaymentRailsERC20RequestV1; + +/** + * @beta + * PaymentTypeUnion is a type of supported payment types + */ +export type PaymentTypeUnion = + | Iden3PaymentCryptoV1 + | Iden3PaymentRailsV1 + | Iden3PaymentRailsERC20V1; diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 12256b32..2b7eccd1 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -47,7 +47,8 @@ import { Iden3PaymentRailsERC20RequestV1, Iden3PaymentRailsRequestV1, Iden3PaymentRequestCryptoV1, - PaymentRequestInfo + PaymentRequestInfo, + PaymentRequestTypeUnion } from '../../src/iden3comm/types/protocol/payment'; import { Contract, ethers, JsonRpcProvider } from 'ethers'; import fetchMock from '@gr2m/fetch-mock'; @@ -380,7 +381,7 @@ describe('payment-request handler', () => { const paymentValidationIntegrationHandlerFunc = async ( txId: string, - data: Iden3PaymentRequestCryptoV1 | Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 + data: PaymentRequestTypeUnion ): Promise => { const rpcProvider = new JsonRpcProvider(RPC_URL); const tx = await rpcProvider.getTransaction(txId); From ca73277c10e16e2a7e8a86114f3f4eb9eed65e30 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 1 Nov 2024 15:17:46 +0200 Subject: [PATCH 45/51] resolve --- src/iden3comm/handlers/payment.ts | 46 +++++++++++-------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 203db4cf..9b99c2c7 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -482,38 +482,24 @@ export class PaymentHandler } ]; + const d: Iden3PaymentRailsRequestV1 = { + type: PaymentRequestDataType.Iden3PaymentRailsRequestV1, + '@context': [ + `https://schema.iden3.io/core/jsonld/payment.jsonld#${type}`, + 'https://w3id.org/security/suites/eip712sig-2021/v1' + ], + recipient, + amount: amount.toString(), + currency, + expirationDate: new Date(expiration).toISOString(), + nonce: nonce.toString(), + metadata: '0x', + proof + }; dataArr.push( type === PaymentRequestDataType.Iden3PaymentRailsRequestV1 - ? { - type, - '@context': [ - `https://schema.iden3.io/core/jsonld/payment.jsonld#${type}`, - 'https://w3id.org/security/suites/eip712sig-2021/v1' - ], - recipient, - amount: amount.toString(), - currency, - expirationDate: expiration, - nonce: nonce.toString(), - metadata: '0x', - proof - } - : { - type, - '@context': [ - `https://schema.iden3.io/core/jsonld/payment.jsonld#${type}`, - 'https://w3id.org/security/suites/eip712sig-2021/v1' - ], - features: features || [], - tokenAddress: tokenAddress || '', - recipient, - amount: amount.toString(), - currency, - expirationDate: expiration, - nonce: nonce.toString(), - metadata: '0x', - proof - } + ? d + : { ...d, type, tokenAddress: tokenAddress || '', features: features || [] } ); } paymentRequestInfo.push({ From 610a262f9e5189ab692fec6c7a0e75dc17b6c00b Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 1 Nov 2024 15:26:48 +0200 Subject: [PATCH 46/51] fix expirationDate value --- src/iden3comm/handlers/payment.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 9b99c2c7..fa7acb2c 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -404,7 +404,7 @@ export class PaymentHandler } /** - * @inheritdoc createERC20PaymentRailsV1#createPaymentRailsV1 + * @inheritdoc IPaymentHandler#createPaymentRailsV1 */ async createPaymentRailsV1( sender: DID, @@ -436,8 +436,8 @@ export class PaymentHandler if (type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 && !tokenAddress) { throw new Error(`failed request. no token address for currency ${currency}`); } - const expiration = - expirationDate ?? new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(); + const expirationTime = + expirationDate ?? new Date(new Date().setHours(new Date().getHours() + 1)).getTime(); const typeUrl = `https://schema.iden3.io/core/json/${type}.json`; const typesFetchResult = await fetch(typeUrl); const types = await typesFetchResult.json(); @@ -447,7 +447,7 @@ export class PaymentHandler ? { recipient, amount, - expirationDate: expiration, + expirationDate: expirationTime, nonce, metadata: '0x' } @@ -455,7 +455,7 @@ export class PaymentHandler tokenAddress, recipient, amount, - expirationDate: expiration, + expirationDate: expirationTime, nonce, metadata: '0x' }; @@ -491,7 +491,7 @@ export class PaymentHandler recipient, amount: amount.toString(), currency, - expirationDate: new Date(expiration).toISOString(), + expirationDate: new Date(expirationTime).toISOString(), nonce: nonce.toString(), metadata: '0x', proof From eac75972e1c32963222bba751532f933f2d32306 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 7 Nov 2024 14:19:12 +0200 Subject: [PATCH 47/51] verifyEIP712TypedData added and check allowedSigners --- src/iden3comm/handlers/payment.ts | 51 ++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index fa7acb2c..b4fc57d9 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -29,7 +29,7 @@ import { SupportedCurrencies, SupportedPaymentProofType } from '../../verifiable'; -import { Signer } from 'ethers'; +import { Signer, ethers } from 'ethers'; /** * @beta @@ -62,6 +62,38 @@ export function createPaymentRequest( return request; } +export async function verifyEIP712TypedData( + data: Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 +): Promise { + const paymentData = + data.type === PaymentRequestDataType.Iden3PaymentRailsRequestV1 + ? { + recipient: data.recipient, + amount: data.amount, + expirationDate: new Date(data.expirationDate).getTime(), + nonce: data.nonce, + metadata: '0x' + } + : { + tokenAddress: data.tokenAddress, + recipient: data.recipient, + amount: data.amount, + expirationDate: new Date(data.expirationDate).getTime(), + nonce: data.nonce, + metadata: '0x' + }; + const proof = Array.isArray(data.proof) ? data.proof[0] : data.proof; + const typesFetchResult = await fetch(proof.eip712.types); + const types = await typesFetchResult.json(); + delete types.EIP712Domain; + const signer = ethers.verifyTypedData(proof.eip712.domain, types, paymentData, proof.proofValue); + const verificationMethodSigner = proof.verificationMethod.split('#')[0].split(':').slice(-1)[0]; + if (signer !== verificationMethodSigner) { + throw new Error(`failed request. invalid signature`); + } + return signer; +} + /** * @beta * PaymentRailsInfo represents payment info for payment rails @@ -194,6 +226,10 @@ export type PaymentHandlerOptions = { export type PaymentHandlerParams = { packerParams: PackerParams; multiChainPaymentConfig?: MultiChainPaymentConfig[]; + /* + * allowed signers for payment request (if not provided, any signer is allowed) + */ + allowedSigners?: string[]; }; /** @@ -428,9 +464,6 @@ export class PaymentHandler throw new Error(`failed request. no config for chain ${chainId}`); } const { recipient, paymentContract, erc20TokenAddressArr } = multiChainConfig; - if (recipient !== (await signer.getAddress())) { - throw new Error('recipient is not the signer'); - } const tokenAddress = erc20TokenAddressArr.find((t) => t.symbol === currency)?.address; if (type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 && !tokenAddress) { @@ -472,7 +505,7 @@ export class PaymentHandler type: SupportedPaymentProofType.EthereumEip712Signature2021, proofPurpose: 'assertionMethod', proofValue: signature, - verificationMethod: `did:pkh:eip155:${chainId}:${recipient}#blockchainAccountId`, + verificationMethod: `did:pkh:eip155:${chainId}:${await signer.getAddress()}#blockchainAccountId`, created: new Date().toISOString(), eip712: { types: typeUrl, @@ -551,6 +584,10 @@ export class PaymentHandler if (data.expirationDate && new Date(data.expirationDate) < new Date()) { throw new Error(`failed request. expired request`); } + const signer = await verifyEIP712TypedData(data); + if (this._params.allowedSigners && !this._params.allowedSigners.includes(signer)) { + throw new Error(`failed request. signer is not in the allowed signers list`); + } const txId = await paymentHandler(data); const proof = Array.isArray(data.proof) ? data.proof[0] : data.proof; return { @@ -573,6 +610,10 @@ export class PaymentHandler throw new Error(`failed request. expired request`); } + const signer = await verifyEIP712TypedData(data); + if (this._params.allowedSigners && !this._params.allowedSigners.includes(signer)) { + throw new Error(`failed request. signer is not in the allowed signers list`); + } if (!data.features?.includes(PaymentFeatures.EIP_2612) && !approveHandler) { throw new Error(`please provide erc20TokenApproveHandler in context for ERC-20 payment type`); } From 434e043af3dd1dd18e7bc23e0d72221a4c564fd3 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 7 Nov 2024 14:24:50 +0200 Subject: [PATCH 48/51] replace asset to with --- rollup.config.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 0bb6f898..604261ec 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,8 +1,8 @@ import commonJS from '@rollup/plugin-commonjs'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import typescript from '@rollup/plugin-typescript'; -import packageJson from './package.json' assert { type: 'json' }; -import tsConfig from './tsconfig.json' assert { type: 'json' }; +import packageJson from './package.json' with { type: 'json' }; +import tsConfig from './tsconfig.json' with { type: 'json' }; import virtual from '@rollup/plugin-virtual'; import json from '@rollup/plugin-json'; const empty = 'export default {}'; From 5d9d8a4b37776539aadc6e8134f13c54afb77944 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 7 Nov 2024 14:57:53 +0200 Subject: [PATCH 49/51] fix unit --- src/iden3comm/handlers/payment.ts | 5 ++-- tests/handlers/payment.test.ts | 42 +++++++++++++++---------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index b4fc57d9..20ace713 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -469,8 +469,9 @@ export class PaymentHandler if (type === PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1 && !tokenAddress) { throw new Error(`failed request. no token address for currency ${currency}`); } - const expirationTime = - expirationDate ?? new Date(new Date().setHours(new Date().getHours() + 1)).getTime(); + const expirationTime = expirationDate + ? new Date(expirationDate).getTime() + : new Date(new Date().setHours(new Date().getHours() + 1)).getTime(); const typeUrl = `https://schema.iden3.io/core/json/${type}.json`; const typesFetchResult = await fetch(typeUrl); const types = await typesFetchResult.json(); diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index 2b7eccd1..c71a9ca2 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -452,22 +452,22 @@ describe('payment-request handler', () => { 'https://w3id.org/security/suites/eip712sig-2021/v1' ], recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - amount: '100', + amount: '40', currency: SupportedCurrencies.ETH_WEI, - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), - nonce: '132', + expirationDate: '2044-11-07T12:45:00.000Z', + nonce: '40024', metadata: '0x', proof: [ { type: SupportedPaymentProofType.EthereumEip712Signature2021, proofPurpose: 'assertionMethod', proofValue: - '0xa05292e9874240c5c2bbdf5a8fefff870c9fc801bde823189fc013d8ce39c7e5431bf0585f01c7e191ea7bbb7110a22e018d7f3ea0ed81a5f6a3b7b828f70f2d1c', + '0x756e11c55fe8f4d2867c2e14e52a06baba29e4b789b4521aafa1623ad96c67aa23dc042bfebd4711ed2db5f145c853a5487b878d8e113e1ede0c41553f6318dd1c', verificationMethod: - 'did:pkh:eip155:0:0x3e1cFE1b83E7C1CdB0c9558236c1f6C7B203C34e#blockchainAccountId', + 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId', created: new Date().toISOString(), eip712: { - types: 'https://example.com/schemas/v1', + types: 'https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json', primaryType: 'Iden3PaymentRailsRequestV1', domain: { name: 'MCPayment', @@ -494,28 +494,28 @@ describe('payment-request handler', () => { { type: PaymentRequestDataType.Iden3PaymentRailsERC20RequestV1, '@context': [ - 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsRequestV1', + 'https://schema.iden3.io/core/jsonld/payment.jsonld#Iden3PaymentRailsERC20RequestV1', 'https://w3id.org/security/suites/eip712sig-2021/v1' ], - tokenAddress: '0x5fb4a5c46d7f2067AA235fbEA350A0261eAF71E3', + tokenAddress: '0x2FE40749812FAC39a0F380649eF59E01bccf3a1A', recipient: '0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', - amount: '1', + amount: '40', currency: 'TST', - expirationDate: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(), - nonce: '32', + expirationDate: '2044-11-07T12:45:00.000Z', + nonce: '40024', metadata: '0x', proof: [ { type: SupportedPaymentProofType.EthereumEip712Signature2021, proofPurpose: 'assertionMethod', proofValue: - '0xa05292e9874240c5c2bbdf5a8fefff870c9fc801bde823189fc013d8ce39c7e5431bf0585f01c7e191ea7bbb7110a22e018d7f3ea0ed81a5f6a3b7b828f70f2d1c', + '0xcb5a8d39a536768fabaafbf17f24954acf7c7d7a6f9a8b75ad5f9c29d324cdaf63de8cebfde508a5a03b60e1a4b765b21f7f3cd60dfed27ce5208432e3fd4c481b', verificationMethod: - 'did:pkh:eip155:0:0x3e1cFE1b83E7C1CdB0c9558236c1f6C7B203C34e#blockchainAccountId', + 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId', created: new Date().toISOString(), eip712: { - types: 'https://example.com/schemas/v1', - primaryType: 'Iden3PaymentRailsRequestV1', + types: 'https://schema.iden3.io/core/json/Iden3PaymentRailsERC20RequestV1.json', + primaryType: 'Iden3PaymentRailsERC20RequestV1', domain: { name: 'MCPayment', version: '1.0.0', @@ -636,7 +636,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentHandlerFuncMock, - nonce: '132' + nonce: '40024' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -659,7 +659,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentHandlerFuncMock, - nonce: '32', + nonce: '40024', erc20TokenApproveHandler: () => Promise.resolve('0x312312334') }); if (!agentMessageBytes) { @@ -803,7 +803,7 @@ describe('payment-request handler', () => { description: 'Iden3PaymentRailsRequestV1 payment-request integration test', chains: [ { - nonce: 10002n, + nonce: 100046n, amount: 100n, currency: SupportedCurrencies.ETH_WEI, chainId: '80002', @@ -828,7 +828,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentIntegrationHandlerFunc('', ''), - nonce: '10002' + nonce: '100046' }); if (!agentMessageBytes) { fail('handlePaymentRequest is not expected null response'); @@ -859,7 +859,7 @@ describe('payment-request handler', () => { description: 'Iden3PaymentRailsERC20RequestV1 payment-request integration test', chains: [ { - nonce: 22003n, + nonce: 22005n, amount: 30n, currency: 'TST', chainId: '80002', @@ -884,7 +884,7 @@ describe('payment-request handler', () => { ); const agentMessageBytes = await paymentHandler.handlePaymentRequest(msgBytesRequest, { paymentHandler: paymentIntegrationHandlerFunc('', ''), - nonce: '22003', + nonce: '22005', erc20TokenApproveHandler: async (data: Iden3PaymentRailsERC20RequestV1) => { const token = new Contract(data.tokenAddress, erc20Abi, ethSigner); const txData = await token.approve( From 21cc80af14902c9a781e2664a7f12c27ac7d4276 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 7 Nov 2024 16:16:50 +0200 Subject: [PATCH 50/51] add did resolution --- src/iden3comm/handlers/payment.ts | 37 +++++++++++++++++++------- tests/handlers/payment.test.ts | 44 +++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 20ace713..0106629c 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -30,6 +30,7 @@ import { SupportedPaymentProofType } from '../../verifiable'; import { Signer, ethers } from 'ethers'; +import { Resolvable } from 'did-resolver'; /** * @beta @@ -63,7 +64,8 @@ export function createPaymentRequest( } export async function verifyEIP712TypedData( - data: Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1 + data: Iden3PaymentRailsRequestV1 | Iden3PaymentRailsERC20RequestV1, + resolver: Resolvable ): Promise { const paymentData = data.type === PaymentRequestDataType.Iden3PaymentRailsRequestV1 @@ -86,12 +88,28 @@ export async function verifyEIP712TypedData( const typesFetchResult = await fetch(proof.eip712.types); const types = await typesFetchResult.json(); delete types.EIP712Domain; - const signer = ethers.verifyTypedData(proof.eip712.domain, types, paymentData, proof.proofValue); - const verificationMethodSigner = proof.verificationMethod.split('#')[0].split(':').slice(-1)[0]; - if (signer !== verificationMethodSigner) { - throw new Error(`failed request. invalid signature`); + const recovered = ethers.verifyTypedData( + proof.eip712.domain, + types, + paymentData, + proof.proofValue + ); + + const { didDocument } = await resolver.resolve(proof.verificationMethod); + if (didDocument?.verificationMethod) { + for (const verificationMethod of didDocument.verificationMethod) { + if ( + verificationMethod.blockchainAccountId?.split(':').slice(-1)[0].toLowerCase() === + recovered.toLowerCase() + ) { + return recovered; + } + } + } else { + throw new Error('resolver_error: issuer DIDDocument does not contain any verificationMethods'); } - return signer; + + throw new Error(`failed request. signature verification failed`); } /** @@ -225,6 +243,7 @@ export type PaymentHandlerOptions = { /** @beta PaymentHandlerParams represents payment handler params */ export type PaymentHandlerParams = { packerParams: PackerParams; + documentResolver: Resolvable; multiChainPaymentConfig?: MultiChainPaymentConfig[]; /* * allowed signers for payment request (if not provided, any signer is allowed) @@ -506,7 +525,7 @@ export class PaymentHandler type: SupportedPaymentProofType.EthereumEip712Signature2021, proofPurpose: 'assertionMethod', proofValue: signature, - verificationMethod: `did:pkh:eip155:${chainId}:${await signer.getAddress()}#blockchainAccountId`, + verificationMethod: `did:pkh:eip155:${chainId}:${await signer.getAddress()}`, created: new Date().toISOString(), eip712: { types: typeUrl, @@ -585,7 +604,7 @@ export class PaymentHandler if (data.expirationDate && new Date(data.expirationDate) < new Date()) { throw new Error(`failed request. expired request`); } - const signer = await verifyEIP712TypedData(data); + const signer = await verifyEIP712TypedData(data, this._params.documentResolver); if (this._params.allowedSigners && !this._params.allowedSigners.includes(signer)) { throw new Error(`failed request. signer is not in the allowed signers list`); } @@ -611,7 +630,7 @@ export class PaymentHandler throw new Error(`failed request. expired request`); } - const signer = await verifyEIP712TypedData(data); + const signer = await verifyEIP712TypedData(data, this._params.documentResolver); if (this._params.allowedSigners && !this._params.allowedSigners.includes(signer)) { throw new Error(`failed request. signer is not in the allowed signers list`); } diff --git a/tests/handlers/payment.test.ts b/tests/handlers/payment.test.ts index c71a9ca2..ec4c8b86 100644 --- a/tests/handlers/payment.test.ts +++ b/tests/handlers/payment.test.ts @@ -28,7 +28,6 @@ import { registerKeyProvidersInMemoryKMS, createIdentity, SEED_USER, - SEED_ISSUER, WALLET_KEY, RPC_URL } from '../helpers'; @@ -53,6 +52,7 @@ import { import { Contract, ethers, JsonRpcProvider } from 'ethers'; import fetchMock from '@gr2m/fetch-mock'; import { fail } from 'assert'; +import { DIDResolutionResult } from 'did-resolver'; describe('payment-request handler', () => { let packageMgr: IPackageManager; @@ -463,8 +463,7 @@ describe('payment-request handler', () => { proofPurpose: 'assertionMethod', proofValue: '0x756e11c55fe8f4d2867c2e14e52a06baba29e4b789b4521aafa1623ad96c67aa23dc042bfebd4711ed2db5f145c853a5487b878d8e113e1ede0c41553f6318dd1c', - verificationMethod: - 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId', + verificationMethod: 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', created: new Date().toISOString(), eip712: { types: 'https://schema.iden3.io/core/json/Iden3PaymentRailsRequestV1.json', @@ -510,8 +509,7 @@ describe('payment-request handler', () => { proofPurpose: 'assertionMethod', proofValue: '0xcb5a8d39a536768fabaafbf17f24954acf7c7d7a6f9a8b75ad5f9c29d324cdaf63de8cebfde508a5a03b60e1a4b765b21f7f3cd60dfed27ce5208432e3fd4c481b', - verificationMethod: - 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId', + verificationMethod: 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', created: new Date().toISOString(), eip712: { types: 'https://schema.iden3.io/core/json/Iden3PaymentRailsERC20RequestV1.json', @@ -553,6 +551,34 @@ describe('payment-request handler', () => { const idWallet = new IdentityWallet(kms, dataStorage, credWallet); const proofService = new ProofService(idWallet, credWallet, circuitStorage, MOCK_STATE_STORAGE); + const didExampleRecovery = { + '@context': [ + 'https://www.w3.org/ns/did/v1', + { + EcdsaSecp256k1RecoveryMethod2020: + 'https://identity.foundation/EcdsaSecp256k1RecoverySignature2020#EcdsaSecp256k1RecoveryMethod2020', + blockchainAccountId: 'https://w3id.org/security#blockchainAccountId' + } + ], + id: 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + verificationMethod: [ + { + id: 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId', + type: 'EcdsaSecp256k1RecoveryMethod2020', + controller: 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a', + blockchainAccountId: 'eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a' + } + ], + authentication: [ + 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId' + ], + assertionMethod: [ + 'did:pkh:eip155:80002:0xE9D7fCDf32dF4772A7EF7C24c76aB40E4A42274a#blockchainAccountId' + ] + }; + const resolveDIDDocument = { + resolve: () => Promise.resolve({ didDocument: didExampleRecovery } as DIDResolutionResult) + }; packageMgr = await getPackageMgr( await circuitStorage.loadCircuitData(CircuitId.AuthV2), proofService.generateAuthV2Inputs.bind(proofService), @@ -562,6 +588,7 @@ describe('payment-request handler', () => { packerParams: { mediaType: MediaType.PlainMessage }, + documentResolver: resolveDIDDocument, multiChainPaymentConfig: [ { chainId: '80002', @@ -590,12 +617,7 @@ describe('payment-request handler', () => { }); userDID = userIdentity.did; - - const issuerIdentity = await createIdentity(idWallet, { - seed: SEED_ISSUER - }); - - issuerDID = issuerIdentity.did; + issuerDID = DID.parse('did:iden3:polygon:amoy:x6x5sor7zpyZX9yNpm8h1rPBDSN9idaEhDj1Qm8Q9'); agentMessageResponse = createProposal(issuerDID, userDID, []); fetchMock.spy(); From d5068a9154277afa81b187363b2f60d4a6fb271e Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Thu, 7 Nov 2024 16:18:44 +0200 Subject: [PATCH 51/51] change err messages --- src/iden3comm/handlers/payment.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iden3comm/handlers/payment.ts b/src/iden3comm/handlers/payment.ts index 0106629c..8225ab1c 100644 --- a/src/iden3comm/handlers/payment.ts +++ b/src/iden3comm/handlers/payment.ts @@ -106,10 +106,10 @@ export async function verifyEIP712TypedData( } } } else { - throw new Error('resolver_error: issuer DIDDocument does not contain any verificationMethods'); + throw new Error('failed request. issuer DIDDocument does not contain any verificationMethods'); } - throw new Error(`failed request. signature verification failed`); + throw new Error(`failed request. no matching verificationMethod`); } /**