diff options
Diffstat (limited to 'core/api-exchange.rst')
-rw-r--r-- | core/api-exchange.rst | 713 |
1 files changed, 712 insertions, 1 deletions
diff --git a/core/api-exchange.rst b/core/api-exchange.rst index 1e9db1d9..12cb4e1a 100644 --- a/core/api-exchange.rst +++ b/core/api-exchange.rst @@ -123,6 +123,15 @@ possibly by using HTTPS. // The exchange's currency. currency: string; + // Purse fee, charged only if a purse is abandoned + // and was not covered by the account limit. + purse_fee: Amount; + + // Non-negative number of concurrent purses that any + // account holder is allowed to create without having + // to pay the purse_fee. + purse_account_limit: integer; + // EdDSA master public key of the exchange, used to sign entries // in ``denoms`` and ``signkeys``. master_public_key: EddsaPublicKey; @@ -918,7 +927,7 @@ exchange. .. ts:def:: TransactionHistoryItem // Union discriminated by the "type" field. - type ReserveTransaction = + type TransactionHistoryItem = | ReserveWithdrawTransaction | ReserveCreditTransaction | ReserveClosingTransaction @@ -1971,3 +1980,705 @@ Refunds // the relevant subset of the transactions. history: CoinSpendHistoryItem[]; } + + + + + + + + + + + + + + + +.. _exchange_w2w: + +-------------------------- +Wallet-to-wallet transfers +-------------------------- + +TODO for the spec: + + * endpoint to create purse with KYC'ed account OR payment, + but without deposit + * endpoint to DELETE account is missing (note: may need + two variants: bank-administrative (offline key?) + and user-driven) + * specify new database schema at exchange + * specify what each signature is made over precisely + * add KYC fee to /keys + * add extended account history fee to /keys + * add purse 'GET' expiration (purse_expiration is when + deposits are no longer allowed, but we may still want + to return the purse status for a bit longer, right? + Or only as part of the coins/accounts status? What about + long-pollers that are active at this expiration time? + [should not DELETE the purse data while the long pollers + are still potentially waking up, so needs a grace period, at least]) + * update wire transfer API to enable WAD IDs (and while we are + at it, should probably also write extended version to allow + _merchants_ to query for their inbound transfers, so spec + for both WADs and regular WTID!) + * specify WAD ID format (keep separte from + WTIDs and reserve public keys!) + * Update coin history replies to include purse actions. + * Merging a purse into an account that is never KYC'ed means + the funds are lost. We need to specify an account expiration + time (after funds are merged, but KYC never happens). + +Discussion: + + * Should the account-withdraw be a separate endpoint from + the reserve withdraw, or should we *extend* the reserve-withdraw + with the "missing KYC" response? + * Should the account-history be a separate endpoint from + the reserve history, or should we *extend* the reserve-history + with the new history entries and the payment option? + * Should we return somewhere a list of the desired + KYC attributes that the wallet should (optionally) + POST to the exchange when initiating KYC? (see: "attributes"). + +Notes: + + * Current API does not allow 'merging' a purse directly into + a regular bank account. The merge MUST go into an account. + * What do we do with KYC'ed accounts where the user fails to + drain the funds? Just keep them? Or do we require getting an + IBAN as part of KYC and 'close' the account that way? + + +Purses +^^^^^^ + + +.. http:GET:: /purses/$PURSE_PUB + + Obtain information about a purse. The request header must + contain a *Purse-Request-Signature*. + + **Request:** + + *Purse-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$PURSE_PRIV``, affirming its authorization to download the purse status. The purpose used MUST be ``TALER_SIGNATURE_PURSE_STATUS`` (NUMBER: TBD). + + :query merge_timeout_ms=NUMBER: *Optional.* If specified, + the exchange + will wait up to ``timeout_ms`` milliseconds for completion + of a merge operation before sending the HTTP response. + :query deposit_timeout_ms=NUMBER: *Optional.* If specified, + the exchange + will wait up to ``timeout_ms`` milliseconds for completion + of a deposit operation before sending the HTTP response. + :query contract=BOOLEAN: *Optional.* If 'false' is specified, + the exchange will not return the encrypted contract, saving + bandwidth for clients that already know it. + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange provides details + about the purse. + The response will include a `PurseStatus` object. + :http:statuscode:`401 Unauthorized`: + The *Purse-Request-Signature* is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The purse is unknown to the exchange. + + **Details:** + + .. ts:def:: PurseStatus + + interface PurseStatus { + + // Total amount that must be paid into the purse. + total_purse_amount: Amount; + + // Total amount deposited into the purse so far. + total_deposit_amount: Amount; + + // Indicative time by which the purse expires + // if it has not been merged into an account. At this + // point, all of the deposits made will be auto-refunded. + purse_expiration: Timestamp; + + // Indicative time at which the exchange is answering the + // status request. Used as part of `exchange_sig`. + status_timestamp: Timestamp; + + // Maximum deposit fees that can be charged under the contract. + max_deposit_fees: Amount; + + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCode; + + // EdDSA signature of the exchange affirming the purse status. + exchange_sig: EddsaSignature; + + // AES-GCM Encrypted contract terms using encryption + // key derived from DH of 'contract_pub' and the 'purse_pub'. + // Optional, may be omitted if not desired by the client. + e_contract_terms?: byte[]; + + // If a merge request was received, information about the + // merge request. Omitted if the purse has not yet received + // a merge request. + merge_request?: MergeRequest; + + } + + +.. http:POST:: /purses/$PURSE_PUB/deposit + + Create a purse without an account, but with associated payment. + + **Request:** The request body must be a `PurseRequest` object. + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange confirms that all + coins were deposited into the purse. + The response will include a `PursePaymentSuccess` object. + :http:statuscode:`202 Accepted`: + The payment was accepted, but insufficient to reach the + specified purse balance. The client should make further + purse deposits before the expiration deadline. + The response will include a `PursePaymentAccepted` object. + :http:statuscode:`401 Unauthorized`: + A coin signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`403 Forbidden`: + The server is denying the operation as a purse with a + different contract or total amount already exists. + This response comes with a standard `PurseConflict` response. + :http:statuscode:`404 Not found`: + Either the denomination key is not recognized (expired or invalid) or + the wire type is not recognized. + :http:statuscode:`409 Conflict`: + The deposit operation has either failed because a coin has insufficient + residual value, or because the same public key of the coin has been + previously used with a different denomination. Which case it is + can be decided by looking at the error code + (``TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS`` or ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``). + The fields of the response are the same in both cases. + The request should not be repeated again with this coin. + In this case, the response is a `PurseDepositDoubleSpendError`. + If the value of all successful coins is below the purse fee, + the exchange may not setup the purse at all. + + + **Details:** + + .. ts:def:: PursePaymentSuccess + + interface PursePaymentSuccess { + + // Total amount paid into the purse. + total_purse_amount: Amount; + + // Total deposit fees charged. + total_deposit_fees: Amount; + + // EdDSA signature of the exchange affirming the payment. + // Signs over the above and the purse public key and + // the hash of the contract terms. + exchange_sig: EddsaSignature; + + } + + .. ts:def:: PursePaymentAccepted + + interface PursePaymentAccepted { + + // Total amount paid so far into the purse, in this + // and previous requests. + total_amount_deposited: Amount; + + // Total amount contributed by the current request. + total_amount_contributed: Amount; + + } + + .. ts:def:: PurseConflict + + interface PurseConflict { + + // Total amount to be paid into the purse as per + // the previous request. + total_purse_amount: Amount; + + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCode; + + // Indicative time by which the purse should expire + // if it has not been merged into an account. At this + // point, all of the deposits made should be + // auto-refunded. + purse_expiration: Timestamp; + + // EdDSA signature of the purse confirming that the + // above details hold for this purse. + purse_sig: EddsaSignature; + + } + + .. ts:def:: PurseRequest + + interface PurseRequest { + + // EdDSA signature of the purse confirming the key + // invariants associated with the purse. + // (amount, h_contract_terms, expiration). + purse_sig: EddsaSignature; + + // Total amount to be paid into the purse. + // Clients may make several requests, i.e. if a + // first request failed with a double-spending error. + // The exchange will confirm the creation of the + // purse once the amount given here is reached. + total_purse_amount: Amount; + + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCode; + + // ECDH contract_public key used to encrypt the contract. + // Optional as the contract terms may already be known + // to the exchange or the other wallet from a different + // interaction. + contract_pub?: EcdhPublicKey; + + // AES-GCM Encrypted contract terms using encryption + // key derived from DH of 'contract_pub' and the 'purse_pub'. + // Optional as the contract terms may already be known + // to the exchange or the other wallet from a different + // interaction. + e_contract_terms?: byte[]; + + // Client-side timestamp of when the payment was made. + timestamp: Timestamp; + + // Indicative time by which the purse should expire + // if it has not been merged into an account. At this + // point, all of the deposits made will be auto-refunded. + purse_expiration: Timestamp; + + // Array of coins being deposited into the purse. + deposits: PurseDeposit[]; + } + + .. ts:def:: PurseDeposit { + + // Public key of the coin being deposited into the purse. + coin_pub: EddsaPublicKey; + + // Amount to be deposited, can be a fraction of the + // coin's total value. + contribution: Amount; + + // Hash of denomination RSA key with which the coin is signed. + denom_pub_hash: HashCode; + + // Exchange's unblinded RSA signature of the coin. + ub_sig: RsaSignature; + + // Signature of `TALER_PurseDepositRequestPS`, + // made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignature; + + } + + .. ts:def:: PurseDepositDoubleSpendError + + interface DepositDoubleSpendError { + // The string constant "insufficient funds". + hint: string; + + // Total amount contributed by the current request. + // Note that some coins may have been successfully + // deposited into the purse, so the total amount + // from these coins is listed here. + total_amount_contributed: Amount; + + // Public keys of coins that could not be deposited + // into the purse, mapped to the coin's histories. + coin_map: EddsaPublicKey -> CoinSpendHistoryItem[]; + } + + + +.. http:POST:: /purses/$PURSE_PUB/merge + + Merge purse with account, adding the value of the purse into + the account. + + **Request:** The request body must be a `MergeRequest` object. + + :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will + wait up to ``timeout_ms`` milliseconds to receive payment before + reporting on the completion of merge operation. Basically + forstalls returning a 202 response for up to timeout milliseconds + to possibly return a 200 response instead. + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange confirms that the + funds were merged into the account. + The response will include a `MergeSuccess` object. + :http:statuscode:`202 Accepted`: + The operation succeeded, the exchange confirms that the + merge request is valid. Alas, the purse was still not + funded and thus the actual merge is delayed. + The response will include a `MergeAccepted` object. + :http:statuscode:`401 Unauthorized`: + Account signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The refund operation failed as we could not find the purse. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`410 Gone`: + The purse has already expired and thus can no longer be merged. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`429 Too Many Requests`: + This account is not at this exchange, has not yet passed the + KYC checks, or it has exceeded the number of open purses. + The client must include payment to create another purse or + wait until existing purses have expired. + + **Details:** + + .. ts:def:: MergeSuccess + + interface MergeSuccess { + + // Amount merged (excluding deposit fees). + merge_amount: Amount; + + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCode; + + // Time at which the merge came into effect. + merge_time: Timestamp; + + // EdDSA signature of the exchange affirming the merge. + // Signs over the above and the account public key. + exchange_sig: EddsaSignature; + + } + + .. ts:def:: MergeAccepted + + interface MergeAccepted { + + // The number of open purses under the given account. + // Useful to calculate how many purses still can be created. + open_purses: integer; + + } + + .. ts:def:: MergeRequest + + interface MergeRequest { + + // payto://-URI of the account the purse is to be merged into. + // Must be of the form: "payto://taler/EXCHANGE_URL/ACCOUNT_PUB". + payto_uri: string; + + // EdDSA signature of the exchange affirming the merge. + account_sig: EddsaSignature; + + // EdDSA signature of the purse private key affirming the merge. + purse_sig: EddsaSignature; + + // Array of payments made to pay for the creation of the + // purse. Can be empty, say if no payment is needed. + payments: CreatePurseDeposit[]; + + } + + .. ts:def:: CreatePurseDeposit { + + // Public key of the coin being used to pay for creating a purse. + coin_pub: EddsaPublicKey; + + // Amount to be deposited, can be a fraction of the + // coin's total value. + contribution: Amount; + + // Hash of denomination RSA key with which the coin is signed. + denom_pub_hash: HashCode; + + // Exchange's unblinded RSA signature of the coin. + ub_sig: RsaSignature; + + // Signature of `TALER_PurseCreateRequestPS`, + // made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignature; + + } + + + + +.. _exchange_accounts: + +Accounts +^^^^^^^^ + +.. http:GET:: /accounts/$ACCOUNT_PUB + + Obtain information about an account. The request header must + contain an *Account-Request-Signature*. + + **Request:** + + *Account-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$ACCOUNT_PRIV``, affirming its authorization to access the account status. The purpose used MUST be ``TALER_SIGNATURE_ACCOUNT_STATUS`` (NUMBER: TBD). + + :query history=BOOLEAN: *Optional.* If specified, the exchange + will return the recent account history. + This is still free of charge. + :query full_history=BOOLEAN: *Optional.* If 'true' is specified, + the exchange will return the full account history. This + may incur a fee that will be charged to the account. + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange provides details + about the account. + The response will include a `AccountHistory` object. + :http:statuscode:`401 Unauthorized`: + The *Account-Request-Signature* is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The account is unknown to the exchange. + + **Details:** + + .. ts:def:: AccountHistory + + interface AccountHistory { + + // Current balance of the account. + balance: Amount; + + // True if the owner of the account currently satisfies + // the required KYC checks. + kyc: boolean; + + // Transaction history for this account. + history: AccountHistoryItem[]; + } + + Objects in the transaction history have the following format: + + .. ts:def:: AccountHistoryItem + + // Union discriminated by the "type" field. + type AccountHistoryItem = + | AccountMergeTransaction + | ReserveWithdrawTransaction + | ReserveCreditTransaction + | ReserveClosingTransaction + | ReserveRecoupTransaction; + + .. ts:def:: AccountMergeTransaction + + interface AccountMergeTransaction { + type: "MERGE"; + + // Amount merged (what was left after fees). + amount: Amount; + + // Purse that was merged. + purse_pub: EddsaPublicKey; + + // Hash of the contract. + h_contract: HashCode; + + // Signature created with the account's private key. + account_sig: EddsaSignature; + + // Signature created with the purse's private key. + purse_sig: EddsaSignature; + + // Deposit fees that were charged to the purse. + deposit_fees: Amount; + } + + +.. http:POST:: /accounts + + Create a new account. + + **Request:** The request body must be a `AccountSetupRequest` object. + + :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will + wait up to ``timeout_ms`` milliseconds for the KYC gateway to + confirm completion of the KYC process. + + **Response:** + + :http:statuscode:`200 Ok`: + The operation succeeded, the exchange confirms that the account + can now be used. + The response will be an `AccountStatus` object. + :http:statuscode:`303 See Other`: + The user should be redirected to the provided location to perform + the required KYC checks to open the account. Afterwards, the + request should be repeated. + :http:statuscode:`504 Gateway Timeout`: + The exchange did not receive a confirmation from the KYC service + within the specified time period. Used when long-polling for the + result. + + **Details:** + + .. ts:def:: AccountSetupRequest + + interface AccountSetupRequest { + + // EdDSA public key for the account. + account_pub: EddsaPublicKey; + + // EdDSA signature of the account affirming the request + // to create the account. + account_sig: EddsaPublicKey; + + // Array of payments made to pay for the creation of the + // account. Can be empty, say if no payment is needed. + payments: CreateAccountDeposit[]; + + // Generic key-value map of attributes about the + // account owner. Useful to pre-fill KYC forms. + // A list of well-known keys is defined in FIXME. + attributes: Object; + + } + + .. ts:def:: CreateAccountDeposit { + + // Public key of the coin being used to pay for creating an + // account. + coin_pub: EddsaPublicKey; + + // Amount to be deposited, can be a fraction of the + // coin's total value. + contribution: Amount; + + // Hash of denomination RSA key with which the coin is signed. + denom_pub_hash: HashCode; + + // Exchange's unblinded RSA signature of the coin. + ub_sig: RsaSignature; + + // Signature of `TALER_AccountCreateRequestPS`, + // made by the customer with the + // `coin's private key <coin-priv>`. + coin_sig: EddsaSignature; + } + + .. ts:def:: AccountKycStatus + + interface AccountKycStatus { + + // Current time of the exchange, used as part of + // what the exchange signs over. + now: Timestamp; + + // EdDSA signature of the exchange affirming the account + // is KYC'ed. + exchange_sig: EddsaSignature; + + } + + +.. http:post:: /accounts/$ACCOUNT_PUB/withdraw + + This endpoint is virtually identical to the withdraw endpoint + for reserves, with the only difference being a new response + code `303 See Other` which is returned for accounts that + have not yet completed the required KYC check. + + **Response**: + + :http:statuscode:`303 See Other`: + The user should be redirected to the provided location to perform + the required KYC checks to open the account. Afterwards, the + request should be repeated. + + +.. _exchange_wads: + + +Wads +^^^^ + +These endpoints are used to manage exchange-to-exchange payments in support of +wallet-to-wallet payments. + + +.. http:GET:: /wads/$WAD_ID + + Obtain information about a wad. + + **Request:** + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange provides details + about the wad. + The response will include a `WadDetails` object. + :http:statuscode:`404 Not found`: + The wad is unknown to the exchange. + + **Details:** + + .. ts:def:: WadDetails + + interface WadDetails { + + // Total transfer amount claimed by the exchange. + total: Amount; + + // Transfers aggregated in the wad. + items: WadItem[]; + } + + Objects in the wad item list have the following format: + + .. ts:def:: WadItem + + interface WadItem { + + // Amount in the purse. + amount: Amount; + + // Purse public key. + purse_pub: EddsaPublicKey; + + // Account public key. + account_pub: EddsaPublicKey; + + // Hash of the contract. + h_contract: HashCode; + + // Signature created with the account's private key. + account_sig: EddsaSignature; + + // Signature created with the purse's private key. + purse_sig: EddsaSignature; + + // Deposit fees that were charged to the purse. + deposit_fees: Amount; + + // Wad fees that was charged to the purse. + wad_fees: Amount; + } |