001package com.box.sdk; 002 003import com.eclipsesource.json.Json; 004import com.eclipsesource.json.JsonArray; 005import com.eclipsesource.json.JsonObject; 006import com.eclipsesource.json.JsonValue; 007import java.net.URL; 008import java.util.ArrayList; 009import java.util.Date; 010import java.util.List; 011 012/** 013 * Represents a Sign Request used by Box Sign. 014 * Sign Requests are used to request e-signatures on documents from signers. 015 * A Sign Request can refer to one or more Box Files and can be sent to one or more Box Sign Request Signers. 016 * 017 * @see <a href="https://developer.box.com/reference/resources/sign-requests/">Box Sign Request</a> 018 * 019 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked 020 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error 021 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p> 022 */ 023@BoxResourceType("sign_request") 024public class BoxSignRequest extends BoxResource { 025 026 /** 027 * The URL template used for operation Sign Request operations. 028 */ 029 public static final URLTemplate SIGN_REQUESTS_URL_TEMPLATE = new URLTemplate("sign_requests"); 030 031 /** 032 * The URL template used for Sign Request operations with a given ID. 033 */ 034 public static final URLTemplate SIGN_REQUEST_URL_TEMPLATE = new URLTemplate("sign_requests/%s"); 035 036 /** 037 * The URL template used to cancel an existing Sign Request. 038 */ 039 public static final URLTemplate SIGN_REQUEST_CANCEL_URL_TEMPLATE = new URLTemplate("sign_requests/%s/cancel"); 040 041 /** 042 * The URL template used to resend an existing Sign Request. 043 */ 044 public static final URLTemplate SIGN_REQUEST_RESEND_URL_TEMPLATE = new URLTemplate("sign_requests/%s/resend"); 045 046 /** 047 * The default limit of entries per response. 048 */ 049 private static final int DEFAULT_LIMIT = 100; 050 051 /** 052 * Constructs a BoxResource for a resource with a given ID. 053 * 054 * @param api the API connection to be used by the resource. 055 * @param id the ID of the resource. 056 */ 057 public BoxSignRequest(BoxAPIConnection api, String id) { 058 super(api, id); 059 } 060 061 /** 062 * Used to create a new sign request using existing BoxFile.Info models. 063 * 064 * @param api the API connection to be used by the created user. 065 * @param sourceFiles the list of BoxFile.Info files to create a signing document from. 066 * @param signers the list of signers for this sign request. 067 * @param parentFolderId the id of the destination folder to place sign request specific data in. 068 * @param optionalParams the optional parameters. 069 * @return the created sign request's info. 070 */ 071 public static BoxSignRequest.Info createSignRequestFromFiles(BoxAPIConnection api, 072 List<BoxFile.Info> sourceFiles, 073 List<BoxSignRequestSigner> signers, 074 String parentFolderId, 075 BoxSignRequestCreateParams optionalParams) { 076 return createSignRequest(api, toBoxSignRequestFiles(sourceFiles), signers, parentFolderId, optionalParams); 077 } 078 079 /** 080 * Used to create a new sign request using BoxFile.Info models. 081 * 082 * @param api the API connection to be used by the created user. 083 * @param sourceFiles the list of BoxFile.Info files to create a signing document from. 084 * @param signers the list of signers for this sign request. 085 * @param parentFolderId the id of the destination folder to place sign request specific data in. 086 * @return the created sign request's info. 087 */ 088 public static BoxSignRequest.Info createSignRequestFromFiles(BoxAPIConnection api, 089 List<BoxFile.Info> sourceFiles, 090 List<BoxSignRequestSigner> signers, 091 String parentFolderId) { 092 093 return createSignRequest(api, toBoxSignRequestFiles(sourceFiles), signers, parentFolderId, null); 094 } 095 096 /** 097 * Used to create a new sign request. 098 * 099 * @param api the API connection to be used by the created user. 100 * @param sourceFiles the list of files to a signing document from. 101 * @param signers the list of signers for this sign request. 102 * @param parentFolderId the id of the destination folder to place sign request specific data in. 103 * @return the created sign request's info. 104 */ 105 public static BoxSignRequest.Info createSignRequest(BoxAPIConnection api, List<BoxSignRequestFile> sourceFiles, 106 List<BoxSignRequestSigner> signers, String parentFolderId) { 107 return createSignRequest(api, sourceFiles, signers, parentFolderId, null); 108 } 109 110 /** 111 * Used to create a new sign request with optional parameters. 112 * 113 * @param api the API connection to be used by the created user. 114 * @param signers the list of signers for this sign request. 115 * @param sourceFiles the list of files to a signing document from. 116 * @param parentFolderId the id of the destination folder to place sign request specific data in. 117 * @param optionalParams the optional parameters. 118 * @return the created sign request's info. 119 */ 120 public static BoxSignRequest.Info createSignRequest(BoxAPIConnection api, List<BoxSignRequestFile> sourceFiles, 121 List<BoxSignRequestSigner> signers, String parentFolderId, 122 BoxSignRequestCreateParams optionalParams) { 123 124 JsonObject requestJSON = new JsonObject(); 125 126 JsonArray sourceFilesJSON = new JsonArray(); 127 for (BoxSignRequestFile sourceFile : sourceFiles) { 128 sourceFilesJSON.add(sourceFile.getJSONObject()); 129 } 130 requestJSON.add("source_files", sourceFilesJSON); 131 132 JsonArray signersJSON = new JsonArray(); 133 for (BoxSignRequestSigner signer : signers) { 134 signersJSON.add(signer.getJSONObject()); 135 } 136 requestJSON.add("signers", signersJSON); 137 138 JsonObject parentFolderJSON = new JsonObject(); 139 parentFolderJSON.add("id", parentFolderId); 140 parentFolderJSON.add("type", "folder"); 141 requestJSON.add("parent_folder", parentFolderJSON); 142 143 if (optionalParams != null) { 144 optionalParams.appendParamsAsJson(requestJSON); 145 } 146 147 URL url = SIGN_REQUESTS_URL_TEMPLATE.build(api.getBaseURL()); 148 BoxJSONRequest request = new BoxJSONRequest(api, url, "POST"); 149 request.setBody(requestJSON.toString()); 150 try (BoxJSONResponse response = request.send()) { 151 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 152 BoxSignRequest signRequest = new BoxSignRequest(api, responseJSON.get("id").asString()); 153 return signRequest.new Info(responseJSON); 154 } 155 } 156 157 /** 158 * Returns all the sign requests. 159 * 160 * @param api the API connection to be used by the resource. 161 * @param fields the fields to retrieve. 162 * @return an iterable with all the sign requests. 163 */ 164 public static Iterable<BoxSignRequest.Info> getAll(final BoxAPIConnection api, String... fields) { 165 return getAll(api, DEFAULT_LIMIT, fields); 166 } 167 168 /** 169 * Returns all the sign requests. 170 * 171 * @param api the API connection to be used by the resource. 172 * @param limit the limit of items per single response. The default value is 100. 173 * @param fields the fields to retrieve. 174 * @return an iterable with all the sign requests. 175 */ 176 public static Iterable<BoxSignRequest.Info> getAll(final BoxAPIConnection api, int limit, String... fields) { 177 QueryStringBuilder queryString = new QueryStringBuilder(); 178 if (fields.length > 0) { 179 queryString.appendParam("fields", fields); 180 } 181 URL url = SIGN_REQUESTS_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), queryString.toString()); 182 return new BoxResourceIterable<BoxSignRequest.Info>(api, url, limit) { 183 184 @Override 185 protected BoxSignRequest.Info factory(JsonObject jsonObject) { 186 BoxSignRequest signRequest = new BoxSignRequest(api, jsonObject.get("id").asString()); 187 return signRequest.new Info(jsonObject); 188 } 189 190 }; 191 } 192 193 private static List<BoxSignRequestFile> toBoxSignRequestFiles(List<BoxFile.Info> sourceFiles) { 194 List<BoxSignRequestFile> files = new ArrayList<>(); 195 for (BoxFile.Info sourceFile : sourceFiles) { 196 BoxSignRequestFile file = BoxSignRequestFile.fromFile(sourceFile); 197 files.add(file); 198 } 199 200 return files; 201 } 202 203 /** 204 * Returns information about this sign request. 205 * 206 * @param fields the fields to retrieve. 207 * @return information about this sign request. 208 */ 209 public BoxSignRequest.Info getInfo(String... fields) { 210 QueryStringBuilder builder = new QueryStringBuilder(); 211 if (fields.length > 0) { 212 builder.appendParam("fields", fields); 213 } 214 URL url = SIGN_REQUEST_URL_TEMPLATE.buildAlphaWithQuery( 215 this.getAPI().getBaseURL(), builder.toString(), this.getID()); 216 BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET"); 217 try (BoxJSONResponse response = request.send()) { 218 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 219 return new BoxSignRequest.Info(responseJSON); 220 } 221 } 222 223 /** 224 * Cancels a sign request if it has not yet been signed or declined. 225 * Any outstanding signers will no longer be able to sign the document. 226 * 227 * @return the cancelled sign request's info. 228 */ 229 public BoxSignRequest.Info cancel() { 230 URL url = SIGN_REQUEST_CANCEL_URL_TEMPLATE.buildAlphaWithQuery(getAPI().getBaseURL(), "", this.getID()); 231 BoxJSONRequest request = new BoxJSONRequest(getAPI(), url, "POST"); 232 try (BoxJSONResponse response = request.send()) { 233 JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); 234 return new BoxSignRequest.Info(responseJSON); 235 } 236 } 237 238 /** 239 * Attempts to resend a Sign Request to all signers that have not signed yet. 240 * There is a 10 minute cooling-off period between each resend request. 241 * If you make a resend call during the cooling-off period, a BoxAPIException will be thrown. 242 */ 243 public void resend() { 244 URL url = SIGN_REQUEST_RESEND_URL_TEMPLATE.buildAlphaWithQuery(getAPI().getBaseURL(), "", this.getID()); 245 BoxAPIRequest request = new BoxAPIRequest(getAPI(), url, "POST"); 246 request.send().close(); 247 } 248 249 /** 250 * Represents a status of the sign request. 251 */ 252 public enum BoxSignRequestStatus { 253 254 /** 255 * Converting status. 256 */ 257 Converting("converting"), 258 259 /** 260 * Created status. 261 */ 262 Created("created"), 263 264 /** 265 * Sent status. 266 */ 267 Sent("sent"), 268 269 /** 270 * Viewed status. 271 */ 272 Viewed("viewed"), 273 274 /** 275 * Signed status. 276 */ 277 Signed("signed"), 278 279 /** 280 * Cancelled status. 281 */ 282 Cancelled("cancelled"), 283 284 /** 285 * Declined status. 286 */ 287 Declined("declined"), 288 289 /** 290 * Error converting status. 291 */ 292 ErrorConverting("error_converting"), 293 294 /** 295 * Error sending status. 296 */ 297 ErrorSending("error_sending"), 298 299 /** 300 * Expired status. 301 */ 302 Expired("expired"), 303 304 /** 305 * Finalizing status. 306 */ 307 Finalizing("finalizing"), 308 309 /** 310 * Error finalizing status. 311 */ 312 ErrorFinalizing("error_finalizing"); 313 314 private final String jsonValue; 315 316 BoxSignRequestStatus(String jsonValue) { 317 this.jsonValue = jsonValue; 318 } 319 320 static BoxSignRequestStatus fromJSONString(String jsonValue) { 321 switch (jsonValue) { 322 case "converting": 323 return Converting; 324 case "created": 325 return Created; 326 case "sent": 327 return Sent; 328 case "viewed": 329 return Viewed; 330 case "signed": 331 return Signed; 332 case "cancelled": 333 return Cancelled; 334 case "declined": 335 return Declined; 336 case "error_converting": 337 return ErrorConverting; 338 case "error_sending": 339 return ErrorSending; 340 case "expired": 341 return Expired; 342 case "finalizing": 343 return Finalizing; 344 case "error_finalizing": 345 return ErrorFinalizing; 346 default: 347 } 348 throw new IllegalArgumentException("The provided JSON value isn't a valid BoxSignRequestStatus value."); 349 } 350 } 351 352 /** 353 * Contains information about the Sign Request. 354 */ 355 public class Info extends BoxResource.Info { 356 357 private boolean isDocumentPreparationNeeded; 358 private boolean areTextSignaturesEnabled; 359 private boolean areDatesEnabled; 360 private BoxSignRequestSignatureColor signatureColor; 361 private String emailSubject; 362 private String emailMessage; 363 private boolean areRemindersEnabled; 364 private List<BoxFile.Info> sourceFiles; 365 private BoxFolder.Info parentFolder; 366 private List<BoxSignRequestSigner> signers; 367 private String name; 368 private List<BoxSignRequestPrefillTag> prefillTags; 369 private Integer daysValid; 370 private String externalId; 371 private String prepareUrl; 372 private BoxFile.Info signingLog; 373 private BoxSignRequestStatus status; 374 private BoxSignRequestSignFiles signFiles; 375 private Date autoExpireAt; 376 private String redirectUrl; 377 private String declinedRedirectUrl; 378 private String templateId; 379 380 /** 381 * Constructs an empty Info object. 382 */ 383 public Info() { 384 super(); 385 } 386 387 /** 388 * Constructs an Info object by parsing information from a JSON string. 389 * 390 * @param json the JSON string to parse. 391 */ 392 public Info(String json) { 393 super(json); 394 } 395 396 /** 397 * Constructs an Info object using an already parsed JSON object. 398 * 399 * @param jsonObject the parsed JSON object. 400 */ 401 Info(JsonObject jsonObject) { 402 super(jsonObject); 403 } 404 405 /** 406 * Indicates if the sender should receive a prepare_url in the response to complete document preparation via UI. 407 * 408 * @return true if document preparation is needed, otherwise false. 409 */ 410 public boolean getIsDocumentPreparationNeeded() { 411 return this.isDocumentPreparationNeeded; 412 } 413 414 /** 415 * Gets the flag indicating if usage of signatures generated by typing (text) is enabled. 416 * 417 * @return true if text signatures are enabled, otherwise false. 418 */ 419 public boolean getAreTextSignaturesEnabled() { 420 return this.areTextSignaturesEnabled; 421 } 422 423 /** 424 * Gets the flag indicating if ability for signer to add dates is enabled. 425 * 426 * @return true if ability for signer to add dates is enabled, otherwise false. 427 */ 428 public boolean getAreDatesEnabled() { 429 return this.areDatesEnabled; 430 } 431 432 /** 433 * Gets the forced, specific color for the signature. 434 * 435 * @return signature color (blue, black, red). 436 */ 437 public BoxSignRequestSignatureColor getSignatureColor() { 438 return this.signatureColor; 439 } 440 441 /** 442 * Gets the subject of the sign request email. 443 * 444 * @return subject of the sign request email. 445 */ 446 public String getEmailSubject() { 447 return this.emailSubject; 448 } 449 450 /** 451 * Gets the message to include in the sign request email. 452 * 453 * @return message of sign request email. 454 */ 455 public String getEmailMessage() { 456 return this.emailMessage; 457 } 458 459 /** 460 * Gets the flag indicating if sending reminders for signers to sign a document on day 3, 8, 13 and 18 461 * (or less if the document has been digitally signed already) is enabled. 462 * 463 * @return true if reminders are enabled, otherwise false. 464 */ 465 public boolean getAreRemindersEnabled() { 466 return this.areRemindersEnabled; 467 } 468 469 /** 470 * Gets the list of files to create a signing document from. 471 * 472 * @return list of files to create a signing document from. 473 */ 474 public List<BoxFile.Info> getSourceFiles() { 475 return this.sourceFiles; 476 } 477 478 /** 479 * Gets the destination folder to place sign request specific data in (copy of source files, signing log etc.). 480 * 481 * @return destination folder to place sign request specific data in. 482 */ 483 public BoxFolder.Info getParentFolder() { 484 return this.parentFolder; 485 } 486 487 /** 488 * Gets the list of signers for this sign request. 489 * 490 * @return list of signers for this sign request. 491 */ 492 public List<BoxSignRequestSigner> getSigners() { 493 return this.signers; 494 } 495 496 /** 497 * Gets the name of this sign request. 498 * 499 * @return name of this sign request. 500 */ 501 public String getName() { 502 return this.name; 503 } 504 505 /** 506 * Gets the list of prefill tags. 507 * 508 * @return list of prefill tags. 509 */ 510 public List<BoxSignRequestPrefillTag> getPrefillTags() { 511 return this.prefillTags; 512 } 513 514 /** 515 * Gets the number of days after which this request will automatically expire if not completed. 516 * 517 * @return number of days after which this request will automatically expire if not completed. 518 */ 519 public Integer getDaysValid() { 520 return this.daysValid; 521 } 522 523 /** 524 * Gets the reference id in an external system that this sign request is related to. 525 * 526 * @return external id. 527 */ 528 public String getExternalId() { 529 return this.externalId; 530 } 531 532 /** 533 * Gets the URL that can be used by the sign request sender to prepare the document through the UI. 534 * 535 * @return prepare url. 536 */ 537 public String getPrepareUrl() { 538 return this.prepareUrl; 539 } 540 541 /** 542 * Gets the reference to a file that will hold a log of all signer activity for this request. 543 * 544 * @return signing log. 545 */ 546 public BoxFile.Info getSigningLog() { 547 return this.signingLog; 548 } 549 550 /** 551 * Gets the status of the sign request. 552 * 553 * @return sign request's status. 554 */ 555 public BoxSignRequestStatus getStatus() { 556 return this.status; 557 } 558 559 /** 560 * List of files that will be signed, which are copies of the original source files. 561 * A new version of these files are created as signers sign and can be downloaded 562 * at any point in the signing process. 563 * 564 * @return sign files. 565 */ 566 public BoxSignRequestSignFiles getSignFiles() { 567 return this.signFiles; 568 } 569 570 /** 571 * Uses days_valid to calculate the date and time that 572 * the sign request will expire, if unsigned. 573 * 574 * @return auto expires at date. 575 */ 576 public Date getAutoExpireAt() { 577 return this.autoExpireAt; 578 } 579 580 /** 581 * Gets the URL that will be redirected to after the signer completes the sign request. 582 * 583 * @return redirect url. 584 */ 585 public String getRedirectUrl() { 586 return this.redirectUrl; 587 } 588 589 /** 590 * Gets the URL that will be redirected to after the signer declines the sign request. 591 * 592 * @return declined redirect url. 593 */ 594 public String getDeclinedRedirectUrl() { 595 return this.declinedRedirectUrl; 596 } 597 598 /** 599 * Gets the id of the template that was used to create this sign request. 600 * 601 * @return sign template id. 602 */ 603 public String getTemplateId() { 604 return this.templateId; 605 } 606 607 /** 608 * {@inheritDoc} 609 */ 610 @Override 611 public BoxSignRequest getResource() { 612 return BoxSignRequest.this; 613 } 614 615 /** 616 * {@inheritDoc} 617 */ 618 @Override 619 void parseJSONMember(JsonObject.Member member) { 620 super.parseJSONMember(member); 621 String memberName = member.getName(); 622 JsonValue value = member.getValue(); 623 try { 624 switch (memberName) { 625 case "is_document_preparation_needed": 626 this.isDocumentPreparationNeeded = value.asBoolean(); 627 break; 628 case "are_text_signatures_enabled": 629 this.areTextSignaturesEnabled = value.asBoolean(); 630 break; 631 case "are_dates_enabled": 632 this.areDatesEnabled = value.asBoolean(); 633 break; 634 case "signature_color": 635 this.signatureColor = BoxSignRequestSignatureColor.fromJSONString(value.asString()); 636 break; 637 case "email_subject": 638 this.emailSubject = value.asString(); 639 break; 640 case "email_message": 641 this.emailMessage = value.asString(); 642 break; 643 case "are_reminders_enabled": 644 this.areRemindersEnabled = value.asBoolean(); 645 break; 646 case "signers": 647 List<BoxSignRequestSigner> signers = new ArrayList<>(); 648 for (JsonValue signerJSON : value.asArray()) { 649 BoxSignRequestSigner signer = new BoxSignRequestSigner(signerJSON.asObject(), getAPI()); 650 signers.add(signer); 651 } 652 this.signers = signers; 653 break; 654 case "source_files": 655 this.sourceFiles = this.getFiles(value.asArray()); 656 break; 657 case "parent_folder": 658 JsonObject folderJSON = value.asObject(); 659 String folderID = folderJSON.get("id").asString(); 660 BoxFolder folder = new BoxFolder(getAPI(), folderID); 661 this.parentFolder = folder.new Info(folderJSON); 662 break; 663 case "name": 664 this.name = value.asString(); 665 break; 666 case "prefill_tags": 667 List<BoxSignRequestPrefillTag> prefillTags = new ArrayList<>(); 668 for (JsonValue prefillTagJSON : value.asArray()) { 669 BoxSignRequestPrefillTag prefillTag = 670 new BoxSignRequestPrefillTag(prefillTagJSON.asObject()); 671 prefillTags.add(prefillTag); 672 } 673 this.prefillTags = prefillTags; 674 break; 675 case "days_valid": 676 this.daysValid = value.asInt(); 677 break; 678 case "external_id": 679 this.externalId = value.asString(); 680 break; 681 case "prepare_url": 682 this.prepareUrl = value.asString(); 683 break; 684 case "signing_log": 685 JsonObject signingLogJSON = value.asObject(); 686 String fileID = signingLogJSON.get("id").asString(); 687 BoxFile file = new BoxFile(getAPI(), fileID); 688 this.signingLog = file.new Info(signingLogJSON); 689 break; 690 case "status": 691 this.status = BoxSignRequestStatus.fromJSONString(value.asString()); 692 break; 693 case "sign_files": 694 JsonObject signFilesJSON = value.asObject(); 695 JsonValue filesArray = signFilesJSON.get("files"); 696 List<BoxFile.Info> signFiles = this.getFiles(filesArray); 697 boolean isReadyForDownload = signFilesJSON.get("is_ready_for_download").asBoolean(); 698 this.signFiles = new BoxSignRequestSignFiles(signFiles, isReadyForDownload); 699 break; 700 case "auto_expire_at": 701 this.autoExpireAt = BoxDateFormat.parse(value.asString()); 702 break; 703 case "redirect_url": 704 this.redirectUrl = value.asString(); 705 break; 706 case "declined_redirect_url": 707 this.declinedRedirectUrl = value.asString(); 708 break; 709 case "template_id": 710 this.templateId = value.asString(); 711 break; 712 default: 713 } 714 } catch (Exception e) { 715 throw new BoxDeserializationException(memberName, value.toString(), e); 716 } 717 } 718 719 private List<BoxFile.Info> getFiles(JsonValue filesArray) { 720 List<BoxFile.Info> files = new ArrayList<>(); 721 for (JsonValue fileJSON : filesArray.asArray()) { 722 String fileID = fileJSON.asObject().get("id").asString(); 723 BoxFile file = new BoxFile(getAPI(), fileID); 724 files.add(file.new Info(fileJSON.asObject())); 725 } 726 return files; 727 } 728 729 /** 730 * List of files that will be signed, which are copies of the original source files. 731 * A new version of these files are created as signers sign and can be downloaded 732 * at any point in the signing process. 733 */ 734 public class BoxSignRequestSignFiles { 735 private final List<BoxFile.Info> files; 736 private final boolean isReadyToDownload; 737 738 /** 739 * Constructs a BoxSignRequestSignFiles. 740 * 741 * @param files list that signing events will occur on. 742 * @param isReadyToDownload indicating whether a change to the document is processing. 743 */ 744 public BoxSignRequestSignFiles(List<BoxFile.Info> files, boolean isReadyToDownload) { 745 this.files = files; 746 this.isReadyToDownload = isReadyToDownload; 747 } 748 749 /** 750 * Gets the list of files that signing events will occur on - these are copies of the original source files. 751 * 752 * @return list of files. 753 */ 754 public List<BoxFile.Info> getFiles() { 755 return this.files; 756 } 757 758 /** 759 * Gets the flag indicating whether a change to the document is processing and the PDF may be out of date. 760 * It is recommended to wait until processing has finished before downloading the PDF. 761 * Webhooks are not sent until processing has been completed. 762 * 763 * @return true if files are ready to download, otherwise false. 764 */ 765 public boolean getIsReadyToDownload() { 766 return this.isReadyToDownload; 767 } 768 } 769 } 770}