# Sample code, software libraries, command line tools, proofs of concept, templates, or other related technology are provided as AWS Content or Third-Party Content under the AWS Customer Agreement, or the relevant written agreement between you and AWS (whichever applies). You should not use this AWS Content or Third-Party Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the AWS Content or Third-Party Content, such as sample code, as appropriate for production grade use based on your specific quality control practices and standards. Deploying AWS Content or Third-Party Content may incur AWS charges for creating or using AWS chargeable resources, such as running Amazon EC2 instances or using Amazon S3 storage.


Parameters:
  PRIVATEKEY:
    Description: Please enter the private key of your Ethereum address (without the leading '0x')
    Type: String
    Default: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Mappings:
  ConfigMap:
    S3Config: 
      Bucket: 'aws-blogs-artifacts-public'
      Prefix: 'artifacts/WEB3-2/'

Resources:
  deadletterencryptionkey:
    Type: AWS::KMS::Key
    Properties:
      Description: KMS key for SQS Dead Letter Queue encryption
      EnableKeyRotation: true
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Allow SQS service to use the key
            Effect: Allow
            Principal:
              Service: sqs.amazonaws.com
            Action:
              - kms:Decrypt
              - kms:GenerateDataKey
              - kms:CreateGrant
              - kms:DescribeKey
            Resource: '*'
          - Sid: Allow Lambda service to use the key
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action:
              - kms:Decrypt
              - kms:GenerateDataKey
            Resource: '*'

  secretsmanagerencryptionkey:
    Type: AWS::KMS::Key
    Properties:
      Description: KMS key for Secrets Manager private key encryption
      EnableKeyRotation: true
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Allow Secrets Manager service to use the key
            Effect: Allow
            Principal:
              Service: secretsmanager.amazonaws.com
            Action:
              - kms:Decrypt
              - kms:DescribeKey
              - kms:Encrypt
              - kms:GenerateDataKey
              - kms:ReEncrypt*
            Resource: '*'
          - Sid: Allow Lambda functions to decrypt secrets
            Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action:
              - kms:Decrypt
              - kms:DescribeKey
            Resource: '*'

  deadletterqueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: usdc-bridge-dlq
      KmsMasterKeyId: !Ref deadletterencryptionkey
      MessageRetentionPeriod: 1209600  

  privatekey:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: 'private_key'
      SecretString: !Sub '{"private_key":"${PRIVATEKEY}"}'
      KmsKeyId: !Ref secretsmanagerencryptionkey

  logwriterpolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: 
              - !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*'

  accessprivatekeypolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - 'secretsmanager:GetSecretValue'
            Resource:
              - !Ref privatekey
          - Effect: Allow
            Action:
              - 'kms:Decrypt'
            Resource:
              - !GetAtt secretsmanagerencryptionkey.Arn

  sqsdeadletterpolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - 'sqs:SendMessage'
              - 'sqs:GetQueueAttributes'
            Resource:
              - !GetAtt deadletterqueue.Arn
          - Effect: Allow
            Action:
              - 'kms:Decrypt'
              - 'kms:GenerateDataKey'
            Resource:
              - !GetAtt deadletterencryptionkey.Arn

  addr2bytes32role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal:
              Service: 
                - lambda.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - !Ref logwriterpolicy
        - !Ref sqsdeadletterpolicy

  getattestationrole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal:
              Service: 
                - lambda.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - !Ref logwriterpolicy
        - !Ref sqsdeadletterpolicy

  approverole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal:
              Service: 
                - lambda.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - !Ref logwriterpolicy
        - !Ref sqsdeadletterpolicy
        - !Ref accessprivatekeypolicy

  receivemessagerole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal:
              Service: 
                - lambda.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - !Ref logwriterpolicy
        - !Ref sqsdeadletterpolicy
        - !Ref accessprivatekeypolicy

  depositforburnrole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal:
              Service: 
                - lambda.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - !Ref logwriterpolicy
        - !Ref sqsdeadletterpolicy
        - !Ref accessprivatekeypolicy

  waitforreceiptrole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal:
              Service: 
                - lambda.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - !Ref logwriterpolicy
        - !Ref sqsdeadletterpolicy

  allowancerole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal:
              Service: 
                - lambda.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - !Ref logwriterpolicy
        - !Ref sqsdeadletterpolicy

  web3layer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      CompatibleArchitectures: 
        - x86_64
      CompatibleRuntimes: 
        - python3.9
      Content:
        S3Bucket: !FindInMap [ConfigMap, S3Config, Bucket]
        S3Key: !Join [ '' , [ !FindInMap [ConfigMap, S3Config, Prefix] , 'web3_layer.zip' ] ]
      Description: 'web3.py 7.6.1'
      LayerName: web3_layer_761
      
  requestslayer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      CompatibleArchitectures: 
        - x86_64
      CompatibleRuntimes: 
        - python3.9
      Content:
        S3Bucket: !FindInMap [ConfigMap, S3Config, Bucket]
        S3Key: !Join [ '' , [ !FindInMap [ConfigMap, S3Config, Prefix] , 'requests_layer.zip' ] ]
      Description: 'requests'
      LayerName: requests_layer      

  addr2bytes32function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures: 
        - x86_64
      Code: 
        S3Bucket: !FindInMap [ConfigMap, S3Config, Bucket]
        S3Key: !Join [ '' , [ !FindInMap [ConfigMap, S3Config, Prefix] , 'addr_to_bytes32_function.zip' ] ]
      Layers:
        - !Ref web3layer
      FunctionName: 'addr_to_bytes32'
      Handler: 'lambda_function.lambda_handler'
      PackageType: Zip
      Role: !GetAtt addr2bytes32role.Arn
      Runtime: python3.9
      Timeout: 3
      ReservedConcurrentExecutions: 10
      DeadLetterConfig: 
        TargetArn: !GetAtt deadletterqueue.Arn

  approvefunction:
    Type: AWS::Lambda::Function
    Properties:
      Architectures: 
        - x86_64
      Code: 
        S3Bucket: !FindInMap [ConfigMap, S3Config, Bucket]
        S3Key: !Join [ '' , [ !FindInMap [ConfigMap, S3Config, Prefix] , 'approve_function.zip' ] ]
      Layers:
        - !Ref web3layer
        - !Sub 'arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:3'
      FunctionName: 'approve'
      Handler: 'lambda_function.lambda_handler'
      PackageType: Zip
      Role: !GetAtt approverole.Arn
      Runtime: python3.9
      Timeout: 120
      ReservedConcurrentExecutions: 10
      DeadLetterConfig: 
        TargetArn: !GetAtt deadletterqueue.Arn

  receivemessagefunction:
    Type: AWS::Lambda::Function
    Properties:
      Architectures: 
        - x86_64
      Code: 
        S3Bucket: !FindInMap [ConfigMap, S3Config, Bucket]
        S3Key: !Join [ '' , [ !FindInMap [ConfigMap, S3Config, Prefix] , 'receive_message_function.zip' ] ]
      Layers:
        - !Ref web3layer
        - !Sub 'arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:3'
        - !Ref requestslayer
      FunctionName: 'receive_message'
      Handler: 'lambda_function.lambda_handler'
      PackageType: Zip
      Role: !GetAtt receivemessagerole.Arn
      Runtime: python3.9
      Timeout: 120
      ReservedConcurrentExecutions: 10
      DeadLetterConfig: 
        TargetArn: !GetAtt deadletterqueue.Arn

  getattestationfunction:
    Type: AWS::Lambda::Function
    Properties:
      Architectures: 
        - x86_64
      Code: 
        S3Bucket: !FindInMap [ConfigMap, S3Config, Bucket]
        S3Key: !Join [ '' , [ !FindInMap [ConfigMap, S3Config, Prefix] , 'get_attestation_function.zip' ] ]
      Layers:
        - !Ref requestslayer
      FunctionName: 'get_attestation'
      Handler: 'lambda_function.lambda_handler'
      PackageType: Zip
      Role: !GetAtt getattestationrole.Arn
      Runtime: python3.9
      Timeout: 3
      ReservedConcurrentExecutions: 10
      DeadLetterConfig: 
        TargetArn: !GetAtt deadletterqueue.Arn

  depositforburnfunction:
    Type: AWS::Lambda::Function
    Properties:
      Architectures: 
        - x86_64
      Code: 
        S3Bucket: !FindInMap [ConfigMap, S3Config, Bucket]
        S3Key: !Join [ '' , [ !FindInMap [ConfigMap, S3Config, Prefix] , 'deposit_for_burn_function.zip' ] ]
      Layers:
        - !Ref web3layer
        - !Sub 'arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python39-x86_64:3'
      FunctionName: 'deposit_for_burn'
      Handler: 'lambda_function.lambda_handler'
      PackageType: Zip
      Role: !GetAtt depositforburnrole.Arn
      Runtime: python3.9
      Timeout: 120
      ReservedConcurrentExecutions: 10
      DeadLetterConfig: 
        TargetArn: !GetAtt deadletterqueue.Arn

  waitforreceiptfunction:
    Type: AWS::Lambda::Function
    Properties:
      Architectures: 
        - x86_64
      Code: 
        S3Bucket: !FindInMap [ConfigMap, S3Config, Bucket]
        S3Key: !Join [ '' , [ !FindInMap [ConfigMap, S3Config, Prefix] , 'wait_for_receipt_function.zip' ] ]
      Layers:
        - !Ref web3layer
      FunctionName: 'wait_for_receipt'
      Handler: 'lambda_function.lambda_handler'
      PackageType: Zip
      Role: !GetAtt waitforreceiptrole.Arn
      Runtime: python3.9
      Timeout: 120
      ReservedConcurrentExecutions: 10
      DeadLetterConfig: 
        TargetArn: !GetAtt deadletterqueue.Arn

  allowancefunction:
    Type: AWS::Lambda::Function
    Properties:
      Architectures: 
        - x86_64
      Code: 
        S3Bucket: !FindInMap [ConfigMap, S3Config, Bucket]
        S3Key: !Join [ '' , [ !FindInMap [ConfigMap, S3Config, Prefix] , 'allowance_function.zip' ] ]
      Layers:
        - !Ref web3layer
      FunctionName: 'allowance'
      Handler: 'lambda_function.lambda_handler'
      PackageType: Zip
      Role: !GetAtt allowancerole.Arn
      Runtime: python3.9
      Timeout: 3
      ReservedConcurrentExecutions: 10
      DeadLetterConfig: 
        TargetArn: !GetAtt deadletterqueue.Arn

  stepfunctionsrole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: states.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaRole
        - !Ref logwriterpolicy

  statemachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      StateMachineName: 'USDC_Bridge'
      RoleArn: !GetAtt stepfunctionsrole.Arn
      DefinitionString: !Sub '{"QueryLanguage":"JSONata","Comment":"A description of my state machine","StartAt":"Handle Missing Input","States":{"Handle Missing Input":{"Type":"Choice","Choices":[{"Next":"Handle Invalid Format","Condition":"{% $exists($states.input.amount) and $exists($states.input.max_fee) and $exists($states.input.source_address) and $exists($states.input.target_address) and $exists($states.input.source_domain) and $exists($states.input.target_domain) %}"}],"Default":"Fail"},"Handle Invalid Format":{"Type":"Choice","Choices":[{"Next":"Begin Transfer Workflow","Assign":{"amount":"{% $states.input.amount %}","max_fee":"{% $states.input.max_fee %}","source_address":"{% $states.input.source_address %}","target_address":"{% $states.input.target_address %}","source_domain":"{% $states.input.source_domain %}","target_domain":"{% $states.input.target_domain %}"},"Condition":"{% $contains($states.input.source_address, /^0x[a-fA-F0-9]{40}$/) and $contains($states.input.target_address, /^0x[a-fA-F0-9]{40}$/) and $contains($string($states.input.amount), /^[0-9]+$/) and \n$contains($string($states.input.max_fee), /^[0-9]+$/) and \n($states.input.amount > $states.input.max_fee) and \n($states.input.source_domain in [\"0\",\"1\",\"6\"]) and \n($states.input.target_domain in [\"0\",\"1\",\"6\"]) %}"}],"Default":"Fail"},"Fail":{"Type":"Fail"},"Begin Transfer Workflow":{"Type":"Parallel","Branches":[{"StartAt":"Invoke addr2bytes32","States":{"Invoke addr2bytes32":{"Type":"Task","Resource":"arn:aws:states:::lambda:invoke","Output":"{% $states.result.Payload %}","Arguments":{"FunctionName":"arn:aws:lambda:us-east-1:${AWS::AccountId}:function:addr_to_bytes32:$LATEST","Payload":{"address":"{% $target_address %}"}},"Retry":[{"ErrorEquals":["Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException","Lambda.TooManyRequestsException"],"IntervalSeconds":1,"MaxAttempts":3,"BackoffRate":2,"JitterStrategy":"FULL"}],"End":true}}},{"StartAt":"Get current allowance","States":{"Get current allowance":{"Type":"Task","Resource":"arn:aws:states:::lambda:invoke","Output":"{% $states.result.Payload %}","Arguments":{"FunctionName":"arn:aws:lambda:us-east-1:${AWS::AccountId}:function:allowance:$LATEST","Payload":{"source_address":"{% $source_address %}","source_domain":"{% $source_domain %}"}},"Retry":[{"ErrorEquals":["Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException","Lambda.TooManyRequestsException"],"IntervalSeconds":1,"MaxAttempts":3,"BackoffRate":2,"JitterStrategy":"FULL"}],"Next":"Verify allowance"},"Verify allowance":{"Type":"Choice","Choices":[{"Next":"Pass","Condition":"{% $states.input.allowance >= $amount %}"}],"Default":"Approve spending"},"Pass":{"Type":"Pass","End":true},"Approve spending":{"Type":"Task","Resource":"arn:aws:states:::lambda:invoke","Output":"{% $states.result.Payload %}","Arguments":{"FunctionName":"arn:aws:lambda:us-east-1:${AWS::AccountId}:function:approve:$LATEST","Payload":{"source_address":"{% $source_address %}","amount":"{% $amount %}","source_domain":"{% $source_domain %}"}},"Retry":[{"ErrorEquals":["Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException","Lambda.TooManyRequestsException"],"IntervalSeconds":1,"MaxAttempts":3,"BackoffRate":2,"JitterStrategy":"FULL"}],"Next":"Wait for transaction receipt"},"Wait for transaction receipt":{"Type":"Task","Resource":"arn:aws:states:::lambda:invoke","Output":"{% $states.result.Payload %}","Arguments":{"FunctionName":"arn:aws:lambda:us-east-1:${AWS::AccountId}:function:wait_for_receipt:$LATEST","Payload":{"tx":"{% $states.input.tx %}","domain":"{% $source_domain %}"}},"Retry":[{"ErrorEquals":["Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException","Lambda.TooManyRequestsException"],"IntervalSeconds":1,"MaxAttempts":3,"BackoffRate":2,"JitterStrategy":"FULL"}],"End":true,"TimeoutSeconds":120}}}],"Next":"Burn USDC"},"Burn USDC":{"Type":"Task","Resource":"arn:aws:states:::lambda:invoke","Arguments":{"FunctionName":"arn:aws:lambda:us-east-1:${AWS::AccountId}:function:deposit_for_burn:$LATEST","Payload":{"source_address":"{% $source_address %}","amount":"{% $amount %}","max_fee":"{% $max_fee %}","source_domain":"{% $source_domain %}","target_domain":"{% $target_domain %}","bytes32_address":"{% $states.input.bytes32_address %}"}},"Retry":[{"ErrorEquals":["Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException","Lambda.TooManyRequestsException"],"IntervalSeconds":1,"MaxAttempts":3,"BackoffRate":2,"JitterStrategy":"FULL"}],"Next":"Wait for transaction receipt (2)","Assign":{"burn_tx":"{% $states.result.Payload.tx %}"},"Output":"{% $states.result.Payload %}"},"Wait for transaction receipt (2)":{"Type":"Task","Resource":"arn:aws:states:::lambda:invoke","Arguments":{"FunctionName":"arn:aws:lambda:us-east-1:${AWS::AccountId}:function:wait_for_receipt:$LATEST","Payload":{"tx":"{% $burn_tx %}","domain":"{% $source_domain %}"}},"Retry":[{"ErrorEquals":["Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException","Lambda.TooManyRequestsException"],"IntervalSeconds":1,"MaxAttempts":3,"BackoffRate":2,"JitterStrategy":"FULL"}],"Next":"Wait","Output":"{% $states.result.Payload %}"},"Wait":{"Type":"Wait","Seconds":5,"Next":"Get attestation"},"Get attestation":{"Type":"Task","Resource":"arn:aws:states:::lambda:invoke","Output":"{% $states.result.Payload %}","Arguments":{"FunctionName":"arn:aws:lambda:us-east-1:${AWS::AccountId}:function:get_attestation:$LATEST","Payload":{"source_domain":"{% $source_domain %}","burn_tx":"{% $burn_tx %}"}},"Retry":[{"ErrorEquals":["Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException","Lambda.TooManyRequestsException"],"IntervalSeconds":1,"MaxAttempts":3,"BackoffRate":2,"JitterStrategy":"FULL"}],"Next":"Is attestation ready?"},"Is attestation ready?":{"Type":"Choice","Choices":[{"Next":"Mint USDC","Assign":{"message":"{% $states.input.messages[0].message %}","attestation":"{% $states.input.messages[0].attestation %}","eventNonce":"{% $states.input.messages[0].eventNonce %}"},"Condition":"{% ($exists($states.input.messages[0].status) and $exists($states.input.messages[0].attestation) and $exists($states.input.messages[0].message) and ($states.input.messages[0].status) = (\"complete\")) %}"}],"Default":"Wait"},"Mint USDC":{"Type":"Task","Resource":"arn:aws:states:::lambda:invoke","Output":"{% $states.result.Payload %}","Arguments":{"FunctionName":"arn:aws:lambda:us-east-1:${AWS::AccountId}:function:receive_message:$LATEST","Payload":{"target_address":"{% $target_address %}","target_domain":"{% $target_domain %}","message":"{% $message %}","attestation":"{% $attestation %}","eventNonce":"{% $eventNonce %}"}},"Retry":[{"ErrorEquals":["Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException","Lambda.TooManyRequestsException"],"IntervalSeconds":1,"MaxAttempts":3,"BackoffRate":2,"JitterStrategy":"FULL"}],"Next":"Is re-attestation needed?"},"Is re-attestation needed?":{"Type":"Choice","Choices":[{"Next":"Wait","Condition":"{% $exists($states.input.reattestation) %}"}],"Default":"Wait for transaction receipt (3)"},"Wait for transaction receipt (3)":{"Type":"Task","Resource":"arn:aws:states:::lambda:invoke","Output":"{% $states.result.Payload %}","Arguments":{"FunctionName":"arn:aws:lambda:us-east-1:${AWS::AccountId}:function:wait_for_receipt:$LATEST","Payload":{"tx":"{% $states.input.tx %}","domain":"{% $target_domain %}"}},"Retry":[{"ErrorEquals":["Lambda.ServiceException","Lambda.AWSLambdaException","Lambda.SdkClientException","Lambda.TooManyRequestsException"],"IntervalSeconds":1,"MaxAttempts":3,"BackoffRate":2,"JitterStrategy":"FULL"}],"Next":"Success"},"Success":{"Type":"Succeed"}},"TimeoutSeconds":900}'
