diff --git a/CODEOWNERS b/CODEOWNERS index 437e3e73..cd8c1a11 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -9,7 +9,7 @@ # The format is described: https://github.blog/2017-07-06-introducing-code-owners/ # These owners will be the default owners for everything in the repo. -* @michaelneale @mistermoe @jiyoontbd @phoebe-lew @KendallWeihe @kirahsapong @diehuxx @decentralgabe +* @michaelneale @mistermoe @jiyoontbd @KendallWeihe @kirahsapong @diehuxx @decentralgabe @angiejones # ----------------------------------------------- diff --git a/hosted/json-schemas/cancel.schema.json b/hosted/json-schemas/cancel.schema.json new file mode 100644 index 00000000..bcd8f3a5 --- /dev/null +++ b/hosted/json-schemas/cancel.schema.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://tbdex.dev/cancel.schema.json", + "type": "object", + "additionalProperties": false, + "properties": { + "reason": { + "type": "string" + } + } + } \ No newline at end of file diff --git a/hosted/json-schemas/message.schema.json b/hosted/json-schemas/message.schema.json index 3f656b26..ebe7c4bc 100644 --- a/hosted/json-schemas/message.schema.json +++ b/hosted/json-schemas/message.schema.json @@ -16,7 +16,7 @@ }, "kind": { "type": "string", - "enum": ["rfq", "quote", "order", "orderstatus", "close"], + "enum": ["rfq", "quote", "order", "orderstatus", "close", "cancel"], "description": "The message kind (e.g. rfq, quote)" }, "id": { diff --git a/hosted/json-schemas/offering.schema.json b/hosted/json-schemas/offering.schema.json index 2f5659a9..d960aefd 100644 --- a/hosted/json-schemas/offering.schema.json +++ b/hosted/json-schemas/offering.schema.json @@ -142,12 +142,31 @@ "requiredClaims": { "type": "object", "description": "PresentationDefinition that describes the credential(s) the PFI requires in order to provide a quote." + }, + "cancellation": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether cancellation is enabled for this offering" + }, + "terms_url": { + "type": "string", + "description": "A link to a page that describes the terms of cancellation" + }, + "terms": { + "type": "string", + "description": "A human-readable description of the terms of cancellation in plaintext" + } + }, + "required": ["enabled"] } }, "required": [ "description", "payin", "payout", - "payoutUnitsPerPayinUnit" + "payoutUnitsPerPayinUnit", + "cancellation" ] } diff --git a/hosted/test-vectors/protocol/vectors/parse-cancel.json b/hosted/test-vectors/protocol/vectors/parse-cancel.json new file mode 100644 index 00000000..bcb50b34 --- /dev/null +++ b/hosted/test-vectors/protocol/vectors/parse-cancel.json @@ -0,0 +1,20 @@ +{ + "description": "Cancel parses from string", + "input": "{\"metadata\": { \"from\": \"did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6InFSUG1TSnRnUmhocklldHphSG1mUnJyaXVMaXhqS29EeDhNeFduREZRaU0ifQ\", \"to\": \"did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6ImxqaDdqbUs2WFY2aVktUnZBRVQ1cEhva21Zem9jZnFhVmc0ODc0MHlwOHcifQ\", \"kind\": \"cancel\", \"id\": \"cancel_01j2fejf5eenyrt6d6xjdkh7ed\", \"exchangeId\": \"rfq_01j2fejf5eeny8gycyf1ft8x3j\", \"createdAt\": \"2024-07-10T23:10:03Z\", \"protocol\": \"1.0\"},\"data\": { \"reason\": \"I don't want to do this anymore\"},\"signature\": \"eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWVDSTZJbkZTVUcxVFNuUm5VbWhvY2tsbGRIcGhTRzFtVW5KeWFYVk1hWGhxUzI5RWVEaE5lRmR1UkVaUmFVMGlmUSMwIn0..sn8IoOx3bmAeaaZPPY3i2BqKS0h9Eydrp_Zkx8czQCmOvhNquXBKxMqH2nd2nsK5XuO_Poqv70aHDCAJblCeCg\"}", + "output": { + "metadata": { + "from": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6InFSUG1TSnRnUmhocklldHphSG1mUnJyaXVMaXhqS29EeDhNeFduREZRaU0ifQ", + "to": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6ImxqaDdqbUs2WFY2aVktUnZBRVQ1cEhva21Zem9jZnFhVmc0ODc0MHlwOHcifQ", + "kind": "cancel", + "id": "cancel_01j2fejf5eenyrt6d6xjdkh7ed", + "exchangeId": "rfq_01j2fejf5eeny8gycyf1ft8x3j", + "createdAt": "2024-07-10T23:10:03Z", + "protocol": "1.0" + }, + "data": { + "reason": "I don't want to do this anymore" + }, + "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWVDSTZJbkZTVUcxVFNuUm5VbWhvY2tsbGRIcGhTRzFtVW5KeWFYVk1hWGhxUzI5RWVEaE5lRmR1UkVaUmFVMGlmUSMwIn0..sn8IoOx3bmAeaaZPPY3i2BqKS0h9Eydrp_Zkx8czQCmOvhNquXBKxMqH2nd2nsK5XuO_Poqv70aHDCAJblCeCg" + }, + "error": false +} \ No newline at end of file diff --git a/hosted/test-vectors/protocol/vectors/parse-offering.json b/hosted/test-vectors/protocol/vectors/parse-offering.json index c43b5dfc..bfcf2d56 100644 --- a/hosted/test-vectors/protocol/vectors/parse-offering.json +++ b/hosted/test-vectors/protocol/vectors/parse-offering.json @@ -1,20 +1,22 @@ { "description": "Offering parses from string", - "input": "{\"metadata\":{\"from\":\"did:dht:c765ni81pupn5zc646xskdsufgokstbsgfwhae8hudsrjushhruy\",\"kind\":\"offering\",\"id\":\"offering_01hwcg100gf8btevp8x3pea75b\",\"createdAt\":\"2024-04-26T06:03:34.289Z\",\"protocol\":\"1.0\"},\"data\":{\"description\":\"Selling BTC for USD\",\"payin\":{\"currencyCode\":\"USD\",\"min\":\"0.0\",\"max\":\"999999.99\",\"methods\":[{\"kind\":\"DEBIT_CARD\",\"requiredPaymentDetails\":{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"cardNumber\":{\"type\":\"string\",\"description\":\"The 16-digit debit card number\",\"minLength\":16,\"maxLength\":16},\"expiryDate\":{\"type\":\"string\",\"description\":\"The expiry date of the card in MM/YY format\",\"pattern\":\"^(0[1-9]|1[0-2])\\\\/([0-9]{2})$\"},\"cardHolderName\":{\"type\":\"string\",\"description\":\"Name of the cardholder as it appears on the card\"},\"cvv\":{\"type\":\"string\",\"description\":\"The 3-digit CVV code\",\"minLength\":3,\"maxLength\":3}},\"required\":[\"cardNumber\",\"expiryDate\",\"cardHolderName\",\"cvv\"],\"additionalProperties\":false}}]},\"payout\":{\"currencyCode\":\"BTC\",\"max\":\"999526.11\",\"methods\":[{\"kind\":\"BTC_ADDRESS\",\"requiredPaymentDetails\":{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"btcAddress\":{\"type\":\"string\",\"description\":\"your Bitcoin wallet address\"}},\"required\":[\"btcAddress\"],\"additionalProperties\":false},\"estimatedSettlementTime\":10}]},\"payoutUnitsPerPayinUnit\":\"0.00003826\",\"requiredClaims\":{\"id\":\"7ce4004c-3c38-4853-968b-e411bafcd945\",\"input_descriptors\":[{\"id\":\"bbdb9b7c-5754-4f46-b63b-590bada959e0\",\"constraints\":{\"fields\":[{\"path\":[\"$.type[*]\"],\"filter\":{\"type\":\"string\",\"pattern\":\"^YoloCredential$\"}}]}}]}},\"signature\":\"eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Yzc2NW5pODFwdXBuNXpjNjQ2eHNrZHN1Zmdva3N0YnNnZndoYWU4aHVkc3JqdXNoaHJ1eSMwIn0..ciWPR5RGHxfQZJUKw3Z2np6EtVJ0wQMZBsNoFjJ-W11x1NmmZCfSkbRAES0eoGlu0Wlyf4waHKvN0UdunakgCw\"}", + "input": "{\"metadata\": {\"from\": \"did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IjdzRDAzOXdITjVybzVhWUxvNjMxaW9aTzVSdjlRS242aGpHamRwZkhFMFkifQ\",\"kind\": \"offering\",\"id\": \"offering_01j2h97kkre7tanx9d4cj1zv6e\",\"createdAt\": \"2024-07-11T16:15:14Z\",\"updatedAt\": \"2024-07-11T16:15:14Z\",\"protocol\": \"1.0\"},\"data\": {\"description\": \"USDC for USD\",\"payoutUnitsPerPayinUnit\": \"1.0\",\"payin\": {\"currencyCode\": \"USD\",\"min\": \"0.1\",\"max\": \"1000\",\"methods\": [{\"kind\": \"DEBIT_CARD\",\"requiredPaymentDetails\": {\"$schema\": \"http://json-schema.org/draft-07/schema#\",\"type\": \"object\",\"properties\": {\"cardNumber\": {\"type\": \"string\",\"description\": \"The 16-digit debit card number\",\"minLength\": 16,\"maxLength\": 16},\"expiryDate\": {\"type\": \"string\",\"description\": \"The expiry date of the card in MM/YY format\",\"pattern\": \"^(0[1-9]|1[0-2])\\\\/([0-9]{2})$\"},\"cardHolderName\": {\"type\": \"string\",\"description\": \"Name of the cardholder as it appears on the card\"},\"cvv\": {\"type\": \"string\",\"description\": \"The 3-digit CVV code\",\"minLength\": 3,\"maxLength\": 3}},\"required\": [\"cardNumber\",\"expiryDate\",\"cardHolderName\",\"cvv\"],\"additionalProperties\": false}}]},\"payout\": {\"currencyCode\": \"USDC\",\"max\": \"5000\",\"methods\": [{\"kind\": \"STORED_BALANCE\",\"estimatedSettlementTime\": 1200}]},\"requiredClaims\": {\"id\": \"foo\",\"name\": \"kyccredential\",\"purpose\": \"To verify the identity of the user\",\"input_descriptors\": [{\"id\": \"1\",\"name\": \"KYC Information\",\"purpose\": \"To verify the identity of the user\",\"constraints\": {\"fields\": [{\"path\": [\"$.type[0]\"],\"filter\": {\"type\": \"string\",\"pattern\": \"KYC\"}}]}}]},\"cancellation\": {\"enabled\": false}},\"signature\": \"eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWVDSTZJamR6UkRBek9YZElUalZ5YnpWaFdVeHZOak14YVc5YVR6VlNkamxSUzI0MmFHcEhhbVJ3WmtoRk1Ga2lmUSMwIn0..M9yF4FtmfeTvmUyutp-k76WFDjuAfJ9fdKdY93Sg1G3KE8KCoFPCQIborK8H22MG2MYsHKZGExEzDWkwXkTbAg\"}", "output": { "metadata": { - "from": "did:dht:c765ni81pupn5zc646xskdsufgokstbsgfwhae8hudsrjushhruy", + "from": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IjdzRDAzOXdITjVybzVhWUxvNjMxaW9aTzVSdjlRS242aGpHamRwZkhFMFkifQ", "kind": "offering", - "id": "offering_01hwcg100gf8btevp8x3pea75b", - "createdAt": "2024-04-26T06:03:34.289Z", + "id": "offering_01j2h97kkre7tanx9d4cj1zv6e", + "createdAt": "2024-07-11T16:15:14Z", + "updatedAt": "2024-07-11T16:15:14Z", "protocol": "1.0" }, "data": { - "description": "Selling BTC for USD", + "description": "USDC for USD", + "payoutUnitsPerPayinUnit": "1.0", "payin": { "currencyCode": "USD", - "min": "0.0", - "max": "999999.99", + "min": "0.1", + "max": "1000", "methods": [ { "kind": "DEBIT_CARD", @@ -56,53 +58,45 @@ ] }, "payout": { - "currencyCode": "BTC", - "max": "999526.11", + "currencyCode": "USDC", + "max": "5000", "methods": [ { - "kind": "BTC_ADDRESS", - "requiredPaymentDetails": { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "btcAddress": { - "type": "string", - "description": "your Bitcoin wallet address" - } - }, - "required": [ - "btcAddress" - ], - "additionalProperties": false - }, - "estimatedSettlementTime": 10 + "kind": "STORED_BALANCE", + "estimatedSettlementTime": 1200 } ] }, - "payoutUnitsPerPayinUnit": "0.00003826", "requiredClaims": { - "id": "7ce4004c-3c38-4853-968b-e411bafcd945", + "id": "foo", + "name": "kyccredential", + "purpose": "To verify the identity of the user", "input_descriptors": [ { - "id": "bbdb9b7c-5754-4f46-b63b-590bada959e0", + "id": "1", + "name": "KYC Information", + "purpose": "To verify the identity of the user", "constraints": { "fields": [ { "path": [ - "$.type[*]" + "$.type[0]" ], "filter": { "type": "string", - "pattern": "^YoloCredential$" + "pattern": "KYC" } } ] } } ] + }, + "cancellation": { + "enabled": false } }, - "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6Yzc2NW5pODFwdXBuNXpjNjQ2eHNrZHN1Zmdva3N0YnNnZndoYWU4aHVkc3JqdXNoaHJ1eSMwIn0..ciWPR5RGHxfQZJUKw3Z2np6EtVJ0wQMZBsNoFjJ-W11x1NmmZCfSkbRAES0eoGlu0Wlyf4waHKvN0UdunakgCw" + "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWVDSTZJamR6UkRBek9YZElUalZ5YnpWaFdVeHZOak14YVc5YVR6VlNkamxSUzI0MmFHcEhhbVJ3WmtoRk1Ga2lmUSMwIn0..M9yF4FtmfeTvmUyutp-k76WFDjuAfJ9fdKdY93Sg1G3KE8KCoFPCQIborK8H22MG2MYsHKZGExEzDWkwXkTbAg" }, "error": false } \ No newline at end of file diff --git a/hosted/test-vectors/protocol/vectors/parse-quote.json b/hosted/test-vectors/protocol/vectors/parse-quote.json index 3f927219..e8e52538 100644 --- a/hosted/test-vectors/protocol/vectors/parse-quote.json +++ b/hosted/test-vectors/protocol/vectors/parse-quote.json @@ -1,37 +1,39 @@ { "description": "Quote parses from string", - "input": "{\"metadata\":{\"exchangeId\":\"rfq_01hw25hn2te5rt4c6fnkb877xg\",\"from\":\"did:dht:3whftgpbdjihx9ze9tdn575zqzm4qwccetnf1ybiibuzad7rrmyy\",\"to\":\"did:dht:qyac4pru9ykcxbrutpaaxkmususxh4wtc4ctt19813zrie8uyy5y\",\"protocol\":\"1.0\",\"kind\":\"quote\",\"id\":\"quote_01hw25hn2vfd0944t9xp7jbx0t\",\"createdAt\":\"2024-04-22T05:48:01.499Z\"},\"data\":{\"expiresAt\":\"2024-04-22T05:48:01.499Z\",\"payin\":{\"currencyCode\":\"BTC\",\"amount\":\"0.01\",\"fee\":\"0.0001\",\"paymentInstruction\":{\"link\":\"tbdex.io/example\",\"instruction\":\"Fake instruction\"}},\"payout\":{\"currencyCode\":\"USD\",\"amount\":\"1000.00\",\"paymentInstruction\":{\"link\":\"tbdex.io/example\",\"instruction\":\"Fake instruction\"}}},\"signature\":\"eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6M3doZnRncGJkamloeDl6ZTl0ZG41NzV6cXptNHF3Y2NldG5mMXliaWlidXphZDdycm15eSMwIn0..sHQBr_s8EEx81hf4I8-qaY_ya4wWtE_wN93YY5-y22bG9RyiX4PAnwJUQJewa8STrK-M38yVNQDaBnqs1O9WAQ\"}", + "input": "{\"metadata\": {\"from\": \"did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6Im83eG85MkxXOWNiZGUtOWRMZUE1ZFhDVjBneUdWVnVtb0xfZVlmVFhtWGsifQ\",\"to\": \"did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6Ii12V195SDFMMWkzUW5vVlNGcXNrMDRGWGd4YVhhRGtHOEV1cF96MmxtWlUifQ\",\"kind\": \"quote\",\"id\": \"quote_01j2f22daeefet5g9ynxg5stm3\",\"exchangeId\": \"rfq_01j2f22daeefeaaqbp61thnxpb\",\"createdAt\": \"2024-07-10T19:31:34Z\",\"protocol\": \"1.0\"},\"data\": {\"expiresAt\": \"2024-07-10T19:31:34Z\",\"payoutUnitsPerPayinUnit\": \"16.665\",\"payin\": {\"currencyCode\": \"USD\",\"subtotal\": \"10\",\"fee\": \"0\",\"total\": \"10\",\"paymentInstruction\": {\"instruction\": \"use link provided\"}},\"payout\": {\"currencyCode\": \"MXN\",\"subtotal\": \"500\",\"fee\": \"0\",\"total\": \"500\",\"paymentInstruction\": {\"instruction\": \"SPEI transfer\"}}},\"signature\": \"eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWVDSTZJbTgzZUc4NU1reFhPV05pWkdVdE9XUk1aVUUxWkZoRFZqQm5lVWRXVm5WdGIweGZaVmxtVkZodFdHc2lmUSMwIn0..SYMZMQwrThsrNHXxHnXoiozCyvtePVOy3kHzQ0Pj3LYYf0h-l6PC2GLblWEtGVJrz27Ct_VdqUnOvm7nF6NLAA\"}", "output": { "metadata": { - "exchangeId": "rfq_01hw25hn2te5rt4c6fnkb877xg", - "from": "did:dht:3whftgpbdjihx9ze9tdn575zqzm4qwccetnf1ybiibuzad7rrmyy", - "to": "did:dht:qyac4pru9ykcxbrutpaaxkmususxh4wtc4ctt19813zrie8uyy5y", - "protocol": "1.0", + "from": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6Im83eG85MkxXOWNiZGUtOWRMZUE1ZFhDVjBneUdWVnVtb0xfZVlmVFhtWGsifQ", + "to": "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6Ii12V195SDFMMWkzUW5vVlNGcXNrMDRGWGd4YVhhRGtHOEV1cF96MmxtWlUifQ", "kind": "quote", - "id": "quote_01hw25hn2vfd0944t9xp7jbx0t", - "createdAt": "2024-04-22T05:48:01.499Z" + "id": "quote_01j2f22daeefet5g9ynxg5stm3", + "exchangeId": "rfq_01j2f22daeefeaaqbp61thnxpb", + "createdAt": "2024-07-10T19:31:34Z", + "protocol": "1.0" }, "data": { - "expiresAt": "2024-04-22T05:48:01.499Z", + "expiresAt": "2024-07-10T19:31:34Z", + "payoutUnitsPerPayinUnit": "16.665", "payin": { - "currencyCode": "BTC", - "amount": "0.01", - "fee": "0.0001", + "currencyCode": "USD", + "subtotal": "10", + "fee": "0", + "total": "10", "paymentInstruction": { - "link": "tbdex.io/example", - "instruction": "Fake instruction" + "instruction": "use link provided" } }, "payout": { - "currencyCode": "USD", - "amount": "1000.00", + "currencyCode": "MXN", + "subtotal": "500", + "fee": "0", + "total": "500", "paymentInstruction": { - "link": "tbdex.io/example", - "instruction": "Fake instruction" + "instruction": "SPEI transfer" } } }, - "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6M3doZnRncGJkamloeDl6ZTl0ZG41NzV6cXptNHF3Y2NldG5mMXliaWlidXphZDdycm15eSMwIn0..sHQBr_s8EEx81hf4I8-qaY_ya4wWtE_wN93YY5-y22bG9RyiX4PAnwJUQJewa8STrK-M38yVNQDaBnqs1O9WAQ" + "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWVDSTZJbTgzZUc4NU1reFhPV05pWkdVdE9XUk1aVUUxWkZoRFZqQm5lVWRXVm5WdGIweGZaVmxtVkZodFdHc2lmUSMwIn0..SYMZMQwrThsrNHXxHnXoiozCyvtePVOy3kHzQ0Pj3LYYf0h-l6PC2GLblWEtGVJrz27Ct_VdqUnOvm7nF6NLAA" }, "error": false } \ No newline at end of file diff --git a/specs/http-api/README.md b/specs/http-api/README.md index 39a4feb3..9f691c23 100644 --- a/specs/http-api/README.md +++ b/specs/http-api/README.md @@ -46,7 +46,7 @@ Version: Draft - [`CreateExchangeRequest`](#createexchangerequest) - [Response](#response-1) - [Errors](#errors) - - [Submit Order/Close](#submit-orderclose) + - [Submit Order/Cancel](#submit-ordercancel) - [Description](#description-2) - [Endpoint](#endpoint-2) - [Protected](#protected-2) @@ -329,12 +329,12 @@ False | 400 | Failed Signature Check | | 409 | Exchange already exists | -## Submit Order/Close +## Submit Order/Cancel ### Description -This endpoint can receive either an Order or a Close message. +This endpoint can receive either an Order or a Cancel message. Alice can submit an Order, which indicates that she wants the PFI to execute on the Quote she received. -Alice can submit a Close, which indicates that Alice is no longer interested in continuing with the exchange. +Alice can submit a Cancel, which indicates that Alice is no longer interested in continuing with the exchange. ### Endpoint `PUT /exchanges/:exchange_id` @@ -345,11 +345,11 @@ False ### Request Body > [!IMPORTANT] > See Order structure [here](../protocol/README.md#order) -> See Close structure [here](../protocol/README.md#close) +> See Cancel structure [here](../protocol/README.md#cancel) ```javascript { - "message": { } // order or close message + "message": { } // order or cancel message } ``` @@ -360,11 +360,11 @@ False | `400: Bad Request` | `{ errors: Error[] }` | ### Errors -| Status | Description | -| ------ | ---------------------- | -| 400 | Failed Signature Check | -| 404 | Exchange not found | -| 409 | Close not allowed | +| Status | Description | +| ------ | --------------------------- | +| 400 | Failed Signature Check | +| 404 | Exchange not found | +| 409 | Order or Cancel not allowed | --- diff --git a/specs/protocol/README.md b/specs/protocol/README.md index 1e7b1c23..5e3f83cf 100644 --- a/specs/protocol/README.md +++ b/specs/protocol/README.md @@ -24,6 +24,7 @@ Version: Draft - [`PayinDetails`](#payindetails) - [`PayoutDetails`](#payoutdetails) - [`PayinMethod`](#payinmethod) + - [`CancellationDetails`](#cancellationdetails) - [`PayoutMethod`](#payoutmethod) - [Reserved `PaymentMethod` Kinds](#reserved-paymentmethod-kinds) - [Example Offering](#example-offering) @@ -46,9 +47,7 @@ Version: Draft - [`SelectedPayoutMethod`](#selectedpayoutmethod) - [`privateData`](#privatedata-1) - [`PrivatePaymentDetails`](#privatepaymentdetails) - - [RFQ example](#rfq-example) - - [`Close`](#close) - - [Example Close](#example-close) + - [Example RFQ](#example-rfq) - [`Quote`](#quote) - [`QuoteDetails`](#quotedetails) - [`PaymentInstruction`](#paymentinstruction) @@ -56,6 +55,10 @@ Version: Draft - [`Order`](#order) - [Example Order](#example-order) - [`OrderStatus`](#orderstatus) + - [`Close`](#close) + - [Example Close](#example-close) + - [`Cancel`](#cancel) + - [Example Cancel](#example-cancel) - [Common Traits](#common-traits) - [ID Generation](#id-generation-1) - [Digests](#digests) @@ -119,6 +122,7 @@ An `Offering` is a resource created by a PFI to define requirements for a given | `payin` | [`PayinDetails`](#payindetails) | Y | Details and options associated to the _payin_ currency | | `payout` | [`PayoutDetails`](#payoutdetails) | Y | Details and options associated to the _payout_ currency | | `requiredClaims` | [`PresentationDefinitionV2`](https://identity.foundation/presentation-exchange/#presentation-definition) | N | Claim(s) required when submitting an RFQ for this offering. | +| `cancellation` | [`CancellationDetails`](#cancellationdetails) | Y | Details about PFI's cancellation policy | #### `PayinDetails` | field | data type | required | description | @@ -148,6 +152,14 @@ An `Offering` is a resource created by a PFI to define requirements for a given | `min` | [`DecimalString`](#decimalstring) | N | minimum amount required to use this payment method. | | `max` | [`DecimalString`](#decimalstring) | N | maximum amount allowed when using this payment method. | +#### `CancellationDetails` +| field | data type | required | description | +| ----------- | ------------------------------------------------------ | -------- | ---------------------------------------------------------------------- | +| `enabled` | boolean | Y | Whether cancellation is enabled for this offering | +| `terms_url` | [`URI`](https://datatracker.ietf.org/doc/html/rfc3986) | N | A link to a page that describes the terms of cancellation | +| `terms` | string | N | A human-readable description of the terms of cancellation in plaintext | + + > [!IMPORTANT] > `kind` should be a unique identifier of an individual payment method @@ -282,6 +294,10 @@ Some payment methods should be consistent across PFIs and therefore have reserve } } ] + }, + "cancellation": { + "enabled": true, + "terms_url": "http://example.com/refund_policy" } }, "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2syc1QyZUtvQWdUUTdzWjY3YTdmRDMzR21jYzZ1UXdaYmlxeWF5Rk1hYkhHI3o2TWtrMnNUMmVLb0FnVFE3c1o2N2E3ZkQzM0dtY2M2dVF3WmJpcXlheUZNYWJIRyJ9..9EBTL3VcajsQzSNOm8GElhcwvYcFGaRp24FTwmC845RCF84Md-ZB-CxdCo7kEjzsAY8OaB55XFSH_8K9vedhAw" @@ -406,7 +422,7 @@ Currency amounts have type `DecimalString`, which is string containing a decimal ## Message Kinds ### `RFQ (Request For Quote)` -> Alice -> PFI: "OK, that offering looks good. Give me a Quote against that Offering, and here is how much USD (payin currency) I want to trade for BTC (payout currency). Here are the credentials you're asking for, the payment method I intend to pay you USD with, and the payment method I expect you to pay me BTC in." +> Alice -> PFI: "Give me a Quote against that Offering, and here is how much payin currency I want to trade for payout currency. Here are the credentials you're asking for, the payment method I want to pay in with, and the payment method I want you to pay out in." | field | data type | required | description | | ------------ | ------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -458,7 +474,7 @@ This table enumerates the structure of `PrivateData` | ---------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `paymentDetails` | object | N | An object containing the properties defined in an Offering's `requiredPaymentDetails` json schema. If `data.payin/payout.paymentDetailsHash` is omitted, then `privateData.payin/payout.paymentDetails` respectively must also be omitted. | -#### RFQ example +#### Example RFQ ```json { "metadata": { @@ -506,41 +522,9 @@ This table enumerates the structure of `PrivateData` } ``` -### `Close` -> Alice -> PFI: "Not interested anymore." or "oops sent by accident" - -> PFI -> Alice: "Can't fulfill what you sent me for whatever reason (e.g. RFQ is erroneous, don't have enough liquidity etc.)" or "Your exchange is completed" - -A `Close` indicates a terminal state; no messages are valid after a `Close`. - -A `Close` can be sent by Alice _or_ the PFI at any point during the exchange, but a `Close` sent by Alice *after* an `Order` but does not guarantee the cancellation of an actively executing order. - -| Field | Data Type | Required | Description | -| --------- | --------- | -------- | ------------------------------------------------------------ | -| `reason` | string | N | an explanation of why the exchange is being closed/completed | -| `success` | boolean | N | indicates whether or not the exchange successfully completed | - -#### Example Close -```json -{ - "metadata": { - "from": "did:key:z6MkvUm6mZxpDsqvyPnpkeWS4fmXD2jJA3TDKq4fDkmR5BD7", - "to": "did:ex:pfi", - "exchangeId": "rfq_01ha83pkgnfxfv41gpa44ckkpz", - "kind": "close", - "id": "close_03ha83trerk6t9tkg7q42s48j", - "createdAt": "2023-09-13T20:28:40.345Z", - "protocol": "1.0" - }, - "data": { - "reason": "Rejecting Quote" - }, - "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3ZVbTZtWnhwRHNxdnlQbnBrZVdTNGZtWEQyakpBM1RES3E0ZkRrbVI1QkQ3I3o2TWt2VW02bVp4cERzcXZ5UG5wa2VXUzRmbVhEMmpKQTNUREtxNGZEa21SNUJENyJ9..tWyGAiuUXFuVvq318Kdz-EJJgCPCWEMO9xVMZD9amjdwPS0p12fkaLwu1PSLxHoXPKSyIbPQnGGZayI_v7tPCA" -} -``` ### `Quote` -> PFI -> Alice: "OK, here's your Quote that describes how much BTC you will receive based on your RFQ. Here's the total fee in USD associated with the payment methods you selected. Here's how to pay us, and how to let us pay you, when you're ready to execute the Quote. This quote expires at X time." +> PFI -> Alice: "OK, here's your Quote that describes how much payout currency you will receive based on your RFQ. Here's the total fee in payin currency associated with the payment methods you selected. Here's how to pay us, and how to let us pay you, when you're ready to execute the Quote. This quote expires at X time." | field | data type | required | description | | ------------------------- | ------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------- | @@ -602,7 +586,6 @@ A `Close` can be sent by Alice _or_ the PFI at any point during the exchange, bu } ``` - ### `Order` > Alice -> PFI: I'm happy with the quote and I want to execute the transaction" @@ -653,6 +636,74 @@ A `Close` can be sent by Alice _or_ the PFI at any point during the exchange, bu } ``` +### `Close` +> PFI -> Alice: "Can't fulfill what you sent me for whatever reason (e.g. RFQ is erroneous, don't have enough liquidity etc.)" or "Your exchange is completed" + +A `Close` indicates a terminal state; no messages are valid after a `Close`. +A `Close` can be sent by the PFI at any point during the exchange, to indicate the PFI will not continue the exchange. +A `Close` can **ONLY** be sent by PFI. + +| Field | Data Type | Required | Description | +| --------- | --------- | -------- | ------------------------------------------------------------ | +| `reason` | string | N | an explanation of why the exchange is being closed/completed | +| `success` | boolean | N | indicates whether or not the exchange successfully completed | + +#### Example Close +```json +{ + "metadata": { + "from": "did:ex:pfi", + "to": "did:key:z6MkvUm6mZxpDsqvyPnpkeWS4fmXD2jJA3TDKq4fDkmR5BD7", + "exchangeId": "rfq_01ha83pkgnfxfv41gpa44ckkpz", + "kind": "close", + "id": "close_03ha83trerk6t9tkg7q42s48j", + "createdAt": "2023-09-13T20:28:40.345Z", + "protocol": "1.0" + }, + "data": { + "reason": "Rejecting RFQ", + "success": false + }, + "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3ZVbTZtWnhwRHNxdnlQbnBrZVdTNGZtWEQyakpBM1RES3E0ZkRrbVI1QkQ3I3o2TWt2VW02bVp4cERzcXZ5UG5wa2VXUzRmbVhEMmpKQTNUREtxNGZEa21SNUJENyJ9..tWyGAiuUXFuVvq318Kdz-EJJgCPCWEMO9xVMZD9amjdwPS0p12fkaLwu1PSLxHoXPKSyIbPQnGGZayI_v7tPCA" +} +``` + +### `Cancel` +> Alice -> PFI: "I would like to back out of the exchange." or "I want a refund". + +A `Cancel` indicates that Alice does not wish to further propagate the exchange. +A `Cancel` can be sent by Alice after sending an RFQ, after receiving a Quote, and after sending an Order. +A `Cancel` can **ONLY** be sent by Alice. + +If a `Cancel` is sent by Alice after she submits an `Order`, that means she would want a refund of her payin. +There is no guarantee of whether `Cancel` will be honored by the PFI - it depends on their cancellation policy, which is outlined in their `Offering` [resource](./README.md#cancellationdetails) + +| field | data type | required | description | +| -------- | --------- | -------- | ----------------------- | +| `reason` | string | N | Reason for cancellation | + + +#### Example Cancel + +```json +{ + "metadata": { + "from": "did:ex:pfi", + "to": "did:key:z6Mkgz7DnRNKVHdF8XavhU6gCcGKBbAUz3FwRjci4bsZQ5U5", + "exchangeId": "rfq_01ha83s5cmeecsm2qg7cvrc9hc", + "kind": "cancel", + "id": "cancel_01ha83s5crff3bmvq3t000cz91", + "createdAt": "2023-09-13T20:30:04.184Z", + "protocol": "1.0" + }, + "data": { + "reason": "wrong delivery address" + }, + "signature": "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa2d6N0RuUk5LVkhkRjhYYXZoVTZnQ2NHS0JiQVV6M0Z3UmpjaTRic1pRNVU1I3o2TWtnejdEblJOS1ZIZEY4WGF2aFU2Z0NjR0tCYkFVejNGd1JqY2k0YnNaUTVVNSJ9..RvOeT5cSurl3I5EO-wg3wVXaNd3mxHhjDUQQj1sNiHz-9u2dI3Hc0ALM-YA6fYLVW9N6XDlSpAAdQd7yZl7rDg" +} +``` + +--- # Common Traits Traits shared amongst both [Resources]() and [Messages]() are defined in this section