Related
The aim is to calculate the uniswap v3 pool's total value locked (TVL).
import json
from web3 import Web3
from collections import namedtuple
infura_url = 'https://mainnet.infura.io/v3/******'
web3 = Web3(Web3.HTTPProvider(infura_url))
def read_json_file(directory:str, file_name: str):
try:
file_path = directory + file_name
f_ = open(file_path, 'r')
except Exception as e:
print(f"Unable to open the {file_path} file")
raise e
else:
json_data = json.loads(f_.read())
return json_data
# uniswap_ETH_USDT.v3
abi = read_json_file('./', 'abis/uniswapV3Pool.json')
address = '0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36'
exchange_contract = web3.eth.contract(address=Web3.toChecksumAddress(address), abi=abi)
Tick = namedtuple("Tick", "liquidityGross liquidityNet feeGrowthOutside0X128 feeGrowthOutside1X128 tickCumulativeOutside secondsPerLiquidityOutsideX128 secondsOutside initialized")
amounts0 = 0
amounts1 = 0
liquidity = 0
slot0 = exchange_contract.functions.slot0().call()
sqrtPriceCurrent = slot0[0] / (1 << 96)
MIN_TICK = -887272
MAX_TICK = 887272
TICK_SPACING = exchange_contract.functions.tickSpacing().call()
def calculate_token0_amount(liquidity, sp, sa, sb):
sp = max(min(sp, sb), sa)
return liquidity * (sb - sp) / (sp * sb)
def calculate_token1_amount(liquidity, sp, sa, sb):
sp = max(min(sp, sb), sa)
return liquidity * (sp - sa)
for tick in range(MIN_TICK, MAX_TICK, TICK_SPACING):
tickRange = Tick(*exchange_contract.functions.ticks(tick).call())
liquidity += tickRange.liquidityNet
sqrtPriceLow = 1.0001 ** (tick // 2)
sqrtPriceHigh = 1.0001 ** ((tick + TICK_SPACING) // 2)
amounts0 += calculate_token0_amount(liquidity, sqrtPriceCurrent, sqrtPriceLow, sqrtPriceHigh)
amounts1 += calculate_token1_amount(liquidity, sqrtPriceCurrent, sqrtPriceLow, sqrtPriceHigh)
print(amounts0, amounts1, tick) # for better output, should correct for the amount of decimals before printing
This does print liquidity in MIN_TICK and MAX_TICK but takes a lot of time and waste web3 calls as it is iterating on zero liquidity ticks also. Right now these are hardcoded, here I want to know what can be the value of min-max so that range does not contain any zero liquidity tick.
Getting pair token balance of contracts
web3.eth.contract(address=token_address,abi=abi).functions.balanceOf(contract_address).call()
and then get current price of each token / USDT by calling function slot0 in pool tokenA/USDT & tokenB/USDT
slot0 = contract.functions.slot0().call()
sqrtPriceCurrent = slot0[0] / (1 << 96)
priceCurrent = sqrtPriceCurrent ** 2
decimal_diff = USDT_decimal - TOKEN_A_decimal
token_price = 10**(-decimal_diff)/( priceCurrent) if token0_address == USDT_address else priceCurrent/(10**decimal_diff)
Finally, TVL = sum(token_balance * token_price)
** Remember: check price from big pool
No offense but you are following a hard way, which needs to use TickBitmap to get the next initialized tick (Remember not all ticks are initialized unless necessary.)
Alternatively the easy way to get a pool's TVL is to query Uniswap V3's subgraph: like
{
pool(id: "0x4e68ccd3e89f51c3074ca5072bbac773960dfa36") {
id
token0 {symbol}
totalValueLockedToken0
token1 {symbol}
totalValueLockedToken1
}
}
(for some reason it doesn't show result if you put checksum address)
or
{
pools(first: 5) {
id
token0 {symbol}
totalValueLockedToken0
token1 {symbol}
totalValueLockedToken1
}
}
I managed to have this code run after few hours of searches but unfortunately, this does not produce the output I wanted which is to get the LP Pool Address in (TOKEN/BNB LP).
Given the Token Address: 0xe56842ed550ff2794f010738554db45e60730371
I wanted to get the BIN/BNB Pool Address: 0xe432afB7283A08Be24E9038C30CA6336A7cC8218.
Any ideas what could be the problem?
from web3 import Web3
from eth_abi.packed import encode_abi_packed
from eth_abi import encode_abi
import eth_abi
"""
Contract: 0xe56842ed550ff2794f010738554db45e60730371
BIN/BNB Address: 0xe432afB7283A08Be24E9038C30CA6336A7cC8218
BIN/BNB LP URL: https://bscscan.com/token/0xe432afB7283A08Be24E9038C30CA6336A7cC8218#balances
"""
CONTRACTS = {"CONTRACT": "0xe56842ed550ff2794f010738554db45e60730371",}
PANCAKE_SWAP_FACTORY = "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73"
PANCAKE_SWAP_ROUTER = "0x10ED43C718714eb63d5aA57B78B54704E256024E"
WBNB_ADDRESS = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
hexadem_= '0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f'
factory = PANCAKE_SWAP_FACTORY
abiEncoded_1 = encode_abi_packed(['address', 'address'], (CONTRACTS['CONTRACT'], WBNB_ADDRESS))
salt_ = Web3.solidityKeccak(['bytes'], ['0x' +abiEncoded_1.hex()])
abiEncoded_2 = encode_abi_packed([ 'address', 'bytes32'], ( factory, salt_))
resPair = Web3.solidityKeccak(['bytes','bytes'], ['0xff' + abiEncoded_2.hex(), hexadem_])[12:]
# resPair is the address for the pancakeswap CONTRACT /WBNB pair
print("Token Contract: ", CONTRACTS)
print("BNB-LP Address: ", resPair.hex()) #-- expecting to get 0xe432afB7283A08Be24E9038C30CA6336A7cC8218
Current Output:
BNB-LP Address: 0xde173b8a63b9641a531de0fbb1c5c9eee3b4bc0c
Expected Output:
Token Contract: 0xe56842ed550ff2794f010738554db45e60730371
BNB-LP Address: 0xe432afB7283A08Be24E9038C30CA6336A7cC8218 #-- correct LP Address
You need to enter the two coins in alphabetical order.
pair_traded = [token_a, token_b] #token_a, token_b are the address's
pair_traded.sort()
hexadem_1 = 0xff
abiEncoded_1 = encode_abi_packed(['address', 'address'], (token_list[0], token_list[1] ))
salt_ = w3.solidityKeccak(['bytes'], ['0x' +abiEncoded_1.hex()])
abiEncoded_2 = encode_abi_packed([ 'address', 'bytes32'], ( factory, salt_))
pair_address = w3.solidityKeccak(['bytes','bytes'], ['0xff' + abiEncoded_2.hex(), pair_code_hash])[12:]
For Pancake Swap the hexadem_ should be like this I think:
hexadem_= '0x00fb7f630766e6a796048ea87d01acd3068e8ff67d078148a3fa3f4a84f69bd5' ## Pancake SWAP
The one in your code is for UniSwap
hexadem_ = '0x00fb7f630766e6a796048ea87d01acd3068e8ff67d078148a3fa3f4a84f69bd5' # This is pancake right hex。
factory = PANCAKE_SWAP_FACTORY
abiEncoded_1 = encode_abi_packed(['address', 'address'], (CONTRACTS['CONTRACT'], WBNB_ADDRESS))
salt_ = Web3.solidityKeccak(['bytes'], ['0x' +abiEncoded_1.hex()])
abiEncoded_2 = encode_abi_packed([ 'address', 'bytes32'], ( factory, salt_))
resPair = Web3.solidityKeccak(['bytes','bytes'], ['0xff' + abiEncoded_2.hex(), hexadem_])[12:]
But the value I output is different from yours, my output value:
Token Contract: {'CONTRACT': '0xe56842ed550ff2794f010738554db45e60730371'}
BNB-LP Address: 0xdbb161367d9a2a852ebeef3cbfcbf2c43b85064b
I figured out you need to make sure both addresses are all lowercase or all uppercase. This makes a difference when sorting. This works for me:
def compute_pool_address(self, token_address_a, token_address_b):
pair_traded = [token_address_a.lower(), token_address_b.lower()]
pair_traded.sort()
hexadem = '0x00fb7f630766e6a796048ea87d01acd3068e8ff67d078148a3fa3f4a84f69bd5'
abiEncoded_1 = encode_abi_packed(['address', 'address'], (pair_traded[0], pair_traded[1]))
salt_ = self.web3.solidityKeccak(['bytes'], ['0x' + abiEncoded_1.hex()])
abiEncoded_2 = encode_abi_packed(['address', 'bytes32'], (PANCAKE_FACTORY_ADDRESS, salt_))
return self.web3.toChecksumAddress(self.web3.solidityKeccak(['bytes', 'bytes'], ['0xff' + abiEncoded_2.hex(), hexadem])[12:])
(self.web3 is a web3.py Web3 instance)
I had similar problems when trying to recreate the functionality of the uniswap contract. It was resolved when I used the eth_abi.abi.encode(python) function inside an expression.
Solidity code what i wanted:
keccak256(
abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encode(key.token0, key.token1, key.fee)),
POOL_INIT_CODE_HASH
)
)
Python code which produces the same result:
encoded_internal_data = abi.encode(['address', 'address', 'uint24'], (token0, token1, fee))
key_hash = web3.solidityKeccak(['bytes'], [encoded_internal_data])
encoded_full_data = encode_abi_packed(
["bytes1", "address", "bytes", "bytes"],
(HexBytes('ff'), UNISWAPV3_FACTORY_ADDRESS, key_hash, HexBytes(UNISWAPV3_POOL_INIT_CODE_HASH))
)
pair_address = web3.solidityKeccak(['bytes'], [encoded_full_data])[12:].hex()
I have generated an Encrypted Text is Python using cryptography
from cryptography.fernet import Fernet
message = "my deep dark secret".encode()
f = Fernet(key)
encrypted = f.encrypt(message)
# decrypting
from cryptography.fernet import Fernet
encrypted = b"...encrypted bytes..."
f = Fernet(key)
decrypted = f.decrypt(encrypted)
ENCRYPTION INFO:
KEY: b'3b-Nqg6ry-jrAuDyVjSwEe8wrdyEPQfPuOQNH1q5olE='
ENC_MESSAGE: b'gAAAAABhBRBGKSwa7AluNJYhwWaHrQGwAA8UpMH8Wtw3tEoTD2E_-nbeoAvxbtBpFiC0ZjbVne_ZetFinKSyMjxwWaPRnXVSVqz5QqpUXp6h-34_TL7BaDs='
Now I'm trying to Decrypt it in Swift but to no luck.
So Far I've Tried CryptoSwift with the following:
func testdec(){
let str = "3b-Nqg6ry-jrAuDyVjSwEe8wrdyEPQfPuOQNH1q5olE="
let ba = "gAAAAABhBRBGKSwa7AluNJYhwWaHrQGwAA8UpMH8Wtw3tEoTD2E_-nbeoAvxbtBpFiC0ZjbVne_ZetFinKSyMjxwWaPRnXVSVqz5QqpUXp6h-34_TL7BaDs="
let encodedString = Base64FS.decodeString(str: String(str.utf8))
print(encodedString.count)
let first4 = String(ba.prefix(25))
let start = first4.index(first4.startIndex, offsetBy: 9)
let end = first4.index(first4.endIndex, offsetBy: 0)
let iv = String(first4[start..<end])
let starta = ba.index(ba.startIndex, offsetBy: 25)
let enda = ba.index(ba.endIndex, offsetBy: -32)
let cipher_text = String(ba[starta..<enda])
let cipher_text_bt: [UInt8] = [UInt8](base64: cipher_text)
print(cipher_text)
print(iv)
let cipher_text_bta: [UInt8] = [UInt8](base64: ba)
// print(encodedString.bytes.count)
// let key_bta: [UInt8] = [UInt8](base64: "RgSADaf8w4v9vokuncyzWRbP5hkdhXSETdxIHLDHtKg=")
// let iv_bt: [UInt8] = [UInt8](base64: "7KUDrsPmb28KQqOWv00KXw==")
// let cipher_text_bt: [UInt8] = [UInt8](base64: "gAAAAABhBQ837KUDrsPmb28KQqOWv00KX2KjsP2ar6lHLqIPUKSvF1WHiruquG-tiAEkrCZZbm-lFR9ZwxsqVcXovmQ3Hv6pWw==")
do{
print("A")
let aes = try AES(key: encodedString, blockMode: CBC(iv: iv.bytes), padding: .pkcs7)
print("B")
let cipherTexta = try aes.decrypt(cipher_text_bt)
print(cipherTexta)
}catch{
print(error)
}
}
OUTPUT:
16
WaHrQGwAA8UpMH8Wtw3tEoTD2E_-nbeoAvxbtBpFiC0ZjbVne_ZetFinKSyMjxw
RBGKSwa7AluNJYhw
A
B
invalidData
Any Help would be appreciated
I've managed to get your cipher text decrypted using only Apple provided sources. If you support iOS 13 and up, I suggest you use CryptoKit to verify the HMAC, but for now, I've adopted a full CommonCrypto solution.
First a minor extension to create Data from base64 URL strings.
import Foundation
import CommonCrypto
extension Data {
init?(base64URL base64: String) {
var base64 = base64
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
if base64.count % 4 != 0 {
base64.append(String(repeating: "=", count: 4 - base64.count % 4))
}
self.init(base64Encoded: base64)
}
}
The decrypt function is a bit obscure, but it supports the very old CommonCrypto syntax. withUnsafeBytes syntax would be cleaner, but this is a quick workaround.
func decrypt(ciphertext: Data, key: Data, iv: Data) -> Data {
var decryptor: CCCryptorRef?
defer {
CCCryptorRelease(decryptor)
}
var key = Array(key)
var iv = Array(iv)
var ciphertext = Array(ciphertext)
CCCryptorCreate(CCOperation(kCCDecrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), &key, key.count, &iv, &decryptor)
var outputBytes = [UInt8](repeating: 0, count: CCCryptorGetOutputLength(decryptor, ciphertext.count, false))
CCCryptorUpdate(decryptor, &ciphertext, ciphertext.count, &outputBytes, outputBytes.count, nil)
var movedBytes = 0
var finalBytes = [UInt8](repeating: 0, count: CCCryptorGetOutputLength(decryptor, 0, true))
CCCryptorFinal(decryptor, &finalBytes, finalBytes.count, &movedBytes)
return Data(outputBytes + finalBytes[0 ..< movedBytes])
}
Then the HMAC. I suggest you use CryptoKit if you can. This function is of course fixed, there might be ways to make this dynamic. For Fernet however, only SHA256 is supported.
func verifyHMAC(_ mac: Data, authenticating data: Data, using key: Data) -> Bool {
var data = Array(data)
var key = Array(key)
var macOut = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), &key, key.count, &data, data.count, &macOut)
return Array(mac) == macOut
}
All of that together comes down to the following code. Note that I do not check the version and/or timestamp, which should be done according to the spec.
let fernetKey = Data(base64URL: "3b-Nqg6ry-jrAuDyVjSwEe8wrdyEPQfPuOQNH1q5olE=")!
let signingKey = fernetKey[0 ..< 16]
let cryptoKey = fernetKey[16 ..< fernetKey.count]
let fernetToken = Data(base64URL: "gAAAAABhBRBGKSwa7AluNJYhwWaHrQGwAA8UpMH8Wtw3tEoTD2E_-nbeoAvxbtBpFiC0ZjbVne_ZetFinKSyMjxwWaPRnXVSVqz5QqpUXp6h-34_TL7BaDs=")!
let version = Data([fernetToken[0]])
let timestamp = fernetToken[1 ..< 9]
let iv = fernetToken[9 ..< 25]
let ciphertext = fernetToken[25 ..< fernetToken.count - 32]
let hmac = fernetToken[fernetToken.count - 32 ..< fernetToken.count]
let plainText = decrypt(ciphertext: ciphertext, key: cryptoKey, iv: iv)
print(plainText, String(data: plainText, encoding: .utf8) ?? "Non utf8")
print(verifyHMAC(hmac, authenticating: version + timestamp + iv + ciphertext, using: signingKey))
Hello stack overflow users
I hope you having a good
so I'm doing this tiny language compiler for my homework
tried using regex
but the output is so weird
First of all, I get an Identifier called 't' which is not used in my input
And it doesn't separate Identifier 'x' from the semicolon
thanks in advance for your help
Here is my input
read x; {input an integer }
if 0 < x then { don’t compute if x <= 0 }
fact := 1;
repeat
fact := fact * x;
x := x - 1
until x = 0;
write fact { output factorial of x }
end
And that's my code using regex
# -*- coding: utf-8 -*-
"""
Created on Wed May 13 04:11:06 2020
#author: PC
"""
class OwnCompiler (object):
def __init__ (self,file):
import re
self.file=open(file,"r").readlines()
self.symbols = {
"+":"PLUS_OP",
"-":"MINUS_OP",
"*":"MUL_OP",
"/":"DIV_OP",
"=":"EQUAL_OP",
"<":"LESS_OP",
">":"GREATER_OP",
"(":"LEFT_PARENTHESIS",
")":"RIGHT_PARENTHESIS",
":=":"ASSIGN",
";":"SEMICOLON",
}
self.commentPattern = re.compile(r".*({\n*\s*.*\s*})")
self.reservePattern = re.compile(r"\s*(read|write|if|then|else|end|repeat|until)+\s*(.*)(then)*")
self.symbolPattern = re.compile(r".*(\+|\*|-|/|=|<|>|\(|\)|;)")
self.identifierSymbol = re.compile(r".*(\w+)\s+(:=)\s+(.*)")
def compileOutput(self):
self.fileWrite=open("output.txt","w")
self.fileWrite.write("Type Token\n==================\n")
for i in self.file :
print(i)
self.getComment(i)
self.getReserveWord(i)
self.getIdentify(i)
self.fileWrite.close()#end
def getComment(self,text):
try:
self.fileWrite.write("COMMENT "+self.commentPattern.match(text).group(1)+"\n")
except:
print("NO_COMMENT")
def getReserveWord(self,text):
self.Compiled = self.reservePattern.match(text)
try:
self.fileWrite.write("RESERVE_WORD "+self.Compiled.group(1)+"\n")
self.getSymbols(self.Compiled.group(2))
try:
self.fileWrite.write("RESERVE_WORD "+self.Compiled.group(3)+"\n")
except:
print("NO_RESERVE_WORD2")
except:
print("NO_RESERVE_WORD")
def getSymbols(self,text):
self.Compiled= self.symbolPattern.match(text)
self.GOT_TOKEN= self.getTokensSymbols(self.Compiled.group())
try:
self.fileWrite.write(self.GOT_TOKEN+" "+self.Compiled.group()+"\n")
except:
print("NO_SYMBOLS")
def getIdentify(self,text):
self.Compiled = self.identifierSymbol.match(text)
try:
self.fileWrite.write("IDENTIFIER "+self.Compiled.group(1)+"\n")
self.getSymbols(text)
for i in self.Compiled.group(3):
if i ==" " :
continue
if self.isNumber(i):
self.fileWrite.write("NUMBER ")
else:
self.fileWrite.write("WORD ")
self.fileWrite.write(self.Compiled.group(3)+"\n")
except:
print("NO_IDENTIFIRES")
def getTokensSymbols(self,symbol):
try:
return self.symbols[symbol]
except:
print("NOT_DEFINED_IN_SYMBOL_DICT")
return "UNKNOWN"
def isNumber(self,text):
try:
int(text)
return True
except:
return False
if __name__ == "__main__":
instance = OwnCompiler("input.txt")
instance.compileOutput()
And here is my output
Type Token
==================
COMMENT { Sample program in TINY language – computes factorial }
COMMENT {input an integer }
RESERVE_WORD read
UNKNOWN x;
COMMENT { don’t compute if x <= 0 }
RESERVE_WORD if
UNKNOWN 0 < x then { don’t compute if x <=
IDENTIFIER t
UNKNOWN fact := 1;
RESERVE_WORD repeat
IDENTIFIER t
UNKNOWN fact := fact * x;
IDENTIFIER x
UNKNOWN x := x -
RESERVE_WORD until
UNKNOWN x = 0;
COMMENT { output factorial of x }
RESERVE_WORD write
RESERVE_WORD end
If you are going to parse a language you need a 'lexer' that will return individual tokens ignoring whitespace and comments. Along these lines, just as an example:
import re, collections
class Lexer(object):
WHITESPACE = r'(?P<WHITESPACE>\s+)'
COMMENT = r'(?P<COMMENT>{[^}]*})'
READ = r'(?P<READ>\bread\b)'
WRITE = r'(?P<WRITE>\bwrite\b)'
IF = r'(?P<IF>\bif\b)'
THEN = r'(?P<THEN>\bthen\b)'
ELSE = r'(?P<ELSE>\belse\b)'
END = r'(?P<END>\bend\b)'
REPEAT = r'(?P<REPEAT>\brepeat\b)'
UNTIL = r'(?P<UNTIL>\buntil\b)'
OPERATOR = r'(?P<OPERATOR>(?:[+*/=<>-]|:=))'
LPAREN = r'(?P<LPAREN>\()'
RPAREN = r'(?P<RPAREN>\))'
IDENTIFIER = r'(?P<IDENTIFIER>[a-z]+)'
INTEGER = r'(?P<INTEGER>\d+)'
SEMICOLON = r'(?P<SEMICOLON>;)'
regex = re.compile('|'.join([
WHITESPACE,
COMMENT,
READ,
WRITE,
IF,
THEN,
ELSE,
END,
REPEAT,
UNTIL,
OPERATOR,
LPAREN,
RPAREN,
IDENTIFIER,
INTEGER,
SEMICOLON
]))
def __init__ (self, file):
def generate_tokens(text):
Token = collections.namedtuple('Token', ['type','value'])
scanner = Lexer.regex.finditer(text)
last_end = 0
for m in scanner:
start = m.start()
end = m.end()
if start != last_end:
# skipped over text to find the next token implies that there was unrecognizable text or an "error token"
text = self.text[last_end:start]
token = Token('ERROR', text)
yield token
last_end = end
token = Token(m.lastgroup, m.group())
if token.type != 'WHITESPACE' and token.type != 'COMMENT':
yield token
yield Token('EOF', '<end-of-file>')
with open(file, "r") as f:
text = f.read()
self._token_generator = generate_tokens(text)
def next_token(self):
# if you call this past the "EOF" token you will get a StopIteration exception
return self._token_generator.__next__()
lexer = Lexer('input.txt')
while True:
token = lexer.next_token()
print(token)
if token.type == 'EOF':
break
Prints:
Token(type='READ', value='read')
Token(type='IDENTIFIER', value='x')
Token(type='SEMICOLON', value=';')
Token(type='IF', value='if')
Token(type='INTEGER', value='0')
Token(type='OPERATOR', value='<')
Token(type='IDENTIFIER', value='x')
Token(type='THEN', value='then')
Token(type='IDENTIFIER', value='fact')
Token(type='OPERATOR', value=':=')
Token(type='INTEGER', value='1')
Token(type='SEMICOLON', value=';')
Token(type='REPEAT', value='repeat')
Token(type='IDENTIFIER', value='fact')
Token(type='OPERATOR', value=':=')
Token(type='IDENTIFIER', value='fact')
Token(type='OPERATOR', value='*')
Token(type='IDENTIFIER', value='x')
Token(type='SEMICOLON', value=';')
Token(type='IDENTIFIER', value='x')
Token(type='OPERATOR', value=':=')
Token(type='IDENTIFIER', value='x')
Token(type='OPERATOR', value='-')
Token(type='INTEGER', value='1')
Token(type='UNTIL', value='until')
Token(type='IDENTIFIER', value='x')
Token(type='OPERATOR', value='=')
Token(type='INTEGER', value='0')
Token(type='SEMICOLON', value=';')
Token(type='WRITE', value='write')
Token(type='IDENTIFIER', value='fact')
Token(type='END', value='end')
Token(type='EOF', value='<end-of-file>')
I'm using the python-fedex module as a light wrapper for the FedEx SOAP API. As part of this, I'm trying to set up a basic example of an international shipment, but I'm getting stuck with the following error message:
fedex.base_service.FedexError: Customs Value is required. (Error code: 2033)
I believe I need to add the products I ship as commodities, incl. their customs value - but I struggle to get this to work. I found this link with some guidance (from C#), but I was unable to get it to work in Python. Any inputs are appreciated!
My code is below:
# !/usr/bin/env python
"""
This example shows how to create a shipment and generate a waybill as output. The variables populated below
represents the minimum required values. You will need to fill all of these, or
risk seeing a SchemaValidationError exception thrown.
Near the bottom of the module, you'll see some different ways to handle the
label data that is returned with the reply.
"""
import logging
import binascii
import datetime
import sys, os
from example_config import CONFIG_OBJ
from fedex.services.ship_service import FedexProcessShipmentRequest
# What kind of file do you want this example to generate?
# Valid choices for this example are PDF, PNG
GENERATE_IMAGE_TYPE = 'PDF'
# Un-comment to see the response from Fedex printed in stdout.
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
# This is the object that will be handling our shipment request.
# We're using the FedexConfig object from example_config.py in this dir.
customer_transaction_id = "*** ShipService Request v17 using Python ***" # Optional transaction_id
shipment = FedexProcessShipmentRequest(CONFIG_OBJ, customer_transaction_id=customer_transaction_id)
# This is very generalized, top-level information.
# REGULAR_PICKUP, REQUEST_COURIER, DROP_BOX, BUSINESS_SERVICE_CENTER or STATION
shipment.RequestedShipment.DropoffType = 'BUSINESS_SERVICE_CENTER'
# See page 355 in WS_ShipService.pdf for a full list. Here are the common ones:
# STANDARD_OVERNIGHT, PRIORITY_OVERNIGHT, FEDEX_GROUND, FEDEX_EXPRESS_SAVER,
# FEDEX_2_DAY, INTERNATIONAL_PRIORITY, SAME_DAY, INTERNATIONAL_ECONOMY
shipment.RequestedShipment.ServiceType = 'INTERNATIONAL_PRIORITY'
# What kind of package this will be shipped in.
# FEDEX_BOX, FEDEX_PAK, FEDEX_TUBE, YOUR_PACKAGING, FEDEX_ENVELOPE
shipment.RequestedShipment.PackagingType = 'FEDEX_ENVELOPE'
# Shipper contact info.
shipment.RequestedShipment.Shipper.Contact.PersonName = 'Shipper Name'
shipment.RequestedShipment.Shipper.Contact.CompanyName = 'Shipper Company'
shipment.RequestedShipment.Shipper.Contact.PhoneNumber = '004512345678'
# Shipper address.
shipment.RequestedShipment.Shipper.Address.StreetLines = ['Shipper Address']
shipment.RequestedShipment.Shipper.Address.City = 'City'
shipment.RequestedShipment.Shipper.Address.StateOrProvinceCode = ''
shipment.RequestedShipment.Shipper.Address.PostalCode = '8270'
shipment.RequestedShipment.Shipper.Address.CountryCode = 'DK'
shipment.RequestedShipment.Shipper.Address.Residential = False
# Recipient contact info.
shipment.RequestedShipment.Recipient.Contact.PersonName = 'US customer X'
shipment.RequestedShipment.Recipient.Contact.CompanyName = 'US company X'
shipment.RequestedShipment.Recipient.Contact.PhoneNumber = '0123456789'
# Recipient address
shipment.RequestedShipment.Recipient.Address.StreetLines = ['668 MURRAY AVE SE']
shipment.RequestedShipment.Recipient.Address.City = 'ROANOKE'
shipment.RequestedShipment.Recipient.Address.StateOrProvinceCode = 'VA'
shipment.RequestedShipment.Recipient.Address.PostalCode = '24013'
shipment.RequestedShipment.Recipient.Address.CountryCode = 'US'
# This is needed to ensure an accurate rate quote with the response. Use AddressValidation to get ResidentialStatus
shipment.RequestedShipment.Recipient.Address.Residential = False
shipment.RequestedShipment.EdtRequestType = 'NONE'
# Senders account information
shipment.RequestedShipment.ShippingChargesPayment.Payor.ResponsibleParty.AccountNumber = CONFIG_OBJ.account_number
# Who pays for the shipment?
# RECIPIENT, SENDER or THIRD_PARTY
shipment.RequestedShipment.ShippingChargesPayment.PaymentType = 'SENDER'
# Specifies the label type to be returned.
# LABEL_DATA_ONLY or COMMON2D
shipment.RequestedShipment.LabelSpecification.LabelFormatType = 'COMMON2D'
# Specifies which format the label file will be sent to you in.
# DPL, EPL2, PDF, PNG, ZPLII
shipment.RequestedShipment.LabelSpecification.ImageType = GENERATE_IMAGE_TYPE
# To use doctab stocks, you must change ImageType above to one of the
# label printer formats (ZPLII, EPL2, DPL).
# See documentation for paper types, there quite a few.
shipment.RequestedShipment.LabelSpecification.LabelStockType = 'PAPER_7X4.75'
# This indicates if the top or bottom of the label comes out of the
# printer first.
# BOTTOM_EDGE_OF_TEXT_FIRST or TOP_EDGE_OF_TEXT_FIRST
# Timestamp in YYYY-MM-DDThh:mm:ss format, e.g. 2002-05-30T09:00:00
shipment.RequestedShipment.ShipTimestamp = datetime.datetime.now().replace(microsecond=0).isoformat()
# BOTTOM_EDGE_OF_TEXT_FIRST, TOP_EDGE_OF_TEXT_FIRST
shipment.RequestedShipment.LabelSpecification.LabelPrintingOrientation = 'TOP_EDGE_OF_TEXT_FIRST'
# Delete the flags we don't want.
# Can be SHIPPING_LABEL_FIRST, SHIPPING_LABEL_LAST or delete
if hasattr(shipment.RequestedShipment.LabelSpecification, 'LabelOrder'):
del shipment.RequestedShipment.LabelSpecification.LabelOrder # Delete, not using.
# Create Weight, in pounds.
package1_weight = shipment.create_wsdl_object_of_type('Weight')
package1_weight.Value = 1.0
package1_weight.Units = "LB"
# Create PackageLineItem
package1 = shipment.create_wsdl_object_of_type('RequestedPackageLineItem')
# BAG, BARREL, BASKET, BOX, BUCKET, BUNDLE, CARTON, CASE, CONTAINER, ENVELOPE etc..
package1.PhysicalPackaging = 'ENVELOPE'
package1.Weight = package1_weight
# Add a signature option for the package using SpecialServicesRequested or comment out.
# SpecialServiceTypes can be APPOINTMENT_DELIVERY, COD, DANGEROUS_GOODS, DRY_ICE, SIGNATURE_OPTION etc..
package1.SpecialServicesRequested.SpecialServiceTypes = 'SIGNATURE_OPTION'
# SignatureOptionType can be ADULT, DIRECT, INDIRECT, NO_SIGNATURE_REQUIRED, SERVICE_DEFAULT
package1.SpecialServicesRequested.SignatureOptionDetail.OptionType = 'SERVICE_DEFAULT'
# This adds the RequestedPackageLineItem WSDL object to the shipment. It
# increments the package count and total weight of the shipment for you.
shipment.add_package(package1)
# If you want to make sure that all of your entered details are valid, you
# can call this and parse it just like you would via send_request(). If
# shipment.response.HighestSeverity == "SUCCESS", your shipment is valid.
# print(shipment.send_validation_request())
# Fires off the request, sets the 'response' attribute on the object.
shipment.send_request()
Your need to add more information.
Insert the following codes before shipment.send_request(), then try again.
shipment.RequestedShipment.CustomsClearanceDetail.CustomsValue.Currency = 'USD'
shipment.RequestedShipment.CustomsClearanceDetail.CustomsValue.Amount = 1.0
shipment.RequestedShipment.CustomsClearanceDetail.DutiesPayment.PaymentType = 'SENDER'
shipment.RequestedShipment.CustomsClearanceDetail.DutiesPayment.Payor.ResponsibleParty.AccountNumber = CONFIG_OBJ.account_number
commodity1 = shipment.create_wsdl_object_of_type('Commodity')
commodity1.Name = 'book'
commodity1.NumberOfPieces = 1
commodity1.Description = '1'
commodity1.CountryOfManufacture = 'CN'
commodity1.HarmonizedCode = '123456789'
commodity1.Quantity = 1.0
commodity1.QuantityUnits = 'EA'
commodity1.Weight.Value = 1.0
commodity1.Weight.Units = "LB"
commodity1.CustomsValue.Currency = 'USD'
commodity1.CustomsValue.Amount = 1.0
commodity1.UnitPrice.Currency = 'USD'
commodity1.UnitPrice.Amount = 1.0
shipment.RequestedShipment.CustomsClearanceDetail.Commodities = [commodity1]
I have added a full international shipment example below, which solves this:
"""
This example shows how to create an international shipment and generate a waybill as output.
The example takes outset in a real practical use case, where electronic trade documents are
used and an existing PDF commercial invoice is added along with product descriptions via ETD.
Further, it adds event notifications to allow for emails to be sent to the end recipient.
The script is comprised of a FedExLabelHelper class with all core functions, and a use case
example with minimal dummy data
"""
from example_config import CONFIG_OBJ
from pathlib import Path
import binascii
import datetime
from fedex.services.ship_service import FedexProcessShipmentRequest
# ----------------------------------------------------
# FedEx class for creating shipments
class FedexLabelHelper:
mCommodities = []
def __init__(self):
pass
# ----------------------------------------------------
# set overall shipment configuration
def setShipmentConfig(
self,
CONFIG_OBJ,
invoice_info,
cust_tran_id="*** ShipService Request v17 using Python ***",
dropoffType="BUSINESS_SERVICE_CENTER",
shippingPaymentType="SENDER",
labelFormatType="COMMON2D",
labelSpecificationImageType="PDF",
labelSpecificationStockType="PAPER_7X4.75",
labelPrintingOrientation="TOP_EDGE_OF_TEXT_FIRST",
LabelOrder="SHIPPING_LABEL_FIRST",
):
self.invoice_info = invoice_info
self.dropoffType = dropoffType
self.serviceType = "INTERNATIONAL_PRIORITY" if invoice_info["ShippingExpress"] == True else "INTERNATIONAL_ECONOMY"
self.mCommodities.clear()
self.CONFIG_OBJ = CONFIG_OBJ
self.shipment = FedexProcessShipmentRequest(CONFIG_OBJ, customer_transaction_id=cust_tran_id)
self.shipment.RequestedShipment.DropoffType = dropoffType
self.shipment.RequestedShipment.ServiceType = self.serviceType
self.shipment.RequestedShipment.ShippingChargesPayment.Payor.ResponsibleParty.AccountNumber = CONFIG_OBJ.account_number
self.shipment.RequestedShipment.ShippingChargesPayment.Payor.ResponsibleParty.Address.CountryCode = "DK"
self.shipment.RequestedShipment.ShippingChargesPayment.PaymentType = shippingPaymentType
labelSpecification = self.shipment.create_wsdl_object_of_type("LabelSpecification")
labelSpecification.LabelFormatType = labelFormatType
labelSpecification.LabelStockType = labelSpecificationStockType
labelSpecification.ImageType = labelSpecificationImageType
labelSpecification.LabelOrder = LabelOrder
labelSpecification.LabelPrintingOrientation = labelPrintingOrientation
self.shipment.RequestedShipment.LabelSpecification = labelSpecification
# ----------------------------------------------------
# set sender information
def setSenderInfo(self, sender):
self.shipment.RequestedShipment.Shipper.Contact.PersonName = sender["Name"]
self.shipment.RequestedShipment.Shipper.Contact.CompanyName = sender["Company"]
self.shipment.RequestedShipment.Shipper.Contact.PhoneNumber = sender["Phone"]
self.shipment.RequestedShipment.Shipper.Contact.EMailAddress = sender["Email"]
self.shipment.RequestedShipment.Shipper.Address.StreetLines = sender["Address"]
self.shipment.RequestedShipment.Shipper.Address.City = sender["City"]
self.shipment.RequestedShipment.Shipper.Address.StateOrProvinceCode = sender["Region"]
self.shipment.RequestedShipment.Shipper.Address.PostalCode = sender["Zip"]
self.shipment.RequestedShipment.Shipper.Address.CountryCode = sender["CountryCode"]
self.shipment.RequestedShipment.Shipper.Address.Residential = sender["Residential"]
ti = self.shipment.create_wsdl_object_of_type("TaxpayerIdentification")
ti.Number = sender["VAT"]
ti.TinType = "BUSINESS_NATIONAL"
self.shipment.RequestedShipment.Shipper.Tins = ti
# ----------------------------------------------------
# upload all documents (invoice and product information)
def upload_all_documents(self):
doc_ids = []
doc_ids.append(self.upload_document(self.invoice_info["InvoicePath"], "COMMERCIAL_INVOICE"))
for pdf in self.invoice_info["Pdfs"]:
doc_ids.append(self.upload_document(pdf, "OTHER"))
return doc_ids
# ----------------------------------------------------
# function for uploading documents as electronic trade documents and getting the response doc IDs
def upload_document(self, path, type):
from fedex.services.document_service import FedexDocumentServiceRequest
# specify prefix for use in attachment naming
if type == "COMMERCIAL_INVOICE":
prefix = "invoice_"
else:
prefix = "product_description_"
uploadRequest = FedexDocumentServiceRequest(self.CONFIG_OBJ)
uploadRequest.OriginCountryCode = "DK"
uploadRequest.DestinationCountryCode = self.shipment.RequestedShipment.Recipient.Address.CountryCode
uploadRequest.Usage = "ELECTRONIC_TRADE_DOCUMENTS"
clientdetails = uploadRequest.create_wsdl_object_of_type("ClientDetail")
clientdetails.AccountNumber = self.CONFIG_OBJ.account_number
clientdetails.MeterNumber = self.CONFIG_OBJ.meter_number
uploadRequest.ClientDetail = clientdetails
webAuthDetails = uploadRequest.create_wsdl_object_of_type("WebAuthenticationDetail")
webAuthDetails.ParentCredential.Key = self.CONFIG_OBJ.key
webAuthDetails.ParentCredential.Password = self.CONFIG_OBJ.password
webAuthDetails.UserCredential.Key = self.CONFIG_OBJ.key
webAuthDetails.UserCredential.Password = self.CONFIG_OBJ.password
uploadRequest.WebAuthenticationDetail = webAuthDetails
docdetails = uploadRequest.create_wsdl_object_of_type("UploadDocumentDetail")
docdetails.LineNumber = 1
docdetails.DocumentType = type
docdetails.FileName = prefix + path
fileContent = open(path, "rb").read()
fileBase64 = binascii.b2a_base64(fileContent)
docdetails.DocumentContent = fileBase64.decode("cp1250")
uploadRequest.Documents = docdetails
uploadRequest.send_request()
doc_id = uploadRequest.response.DocumentStatuses[0].DocumentId
return doc_id
# ----------------------------------------------------
# set recipient information
def setRecipientInfo(self, recipient):
self.shipment.RequestedShipment.Recipient.Contact.PersonName = recipient["Name"]
self.shipment.RequestedShipment.Recipient.Contact.CompanyName = recipient["Company"]
self.shipment.RequestedShipment.Recipient.Contact.PhoneNumber = recipient["Phone"]
self.shipment.RequestedShipment.Recipient.Contact.EMailAddress = recipient["Email"]
self.shipment.RequestedShipment.Recipient.Address.StreetLines = recipient["Address"]
self.shipment.RequestedShipment.Recipient.Address.City = recipient["City"]
self.shipment.RequestedShipment.Recipient.Address.StateOrProvinceCode = recipient["Region"]
self.shipment.RequestedShipment.Recipient.Address.PostalCode = recipient["Zip"]
self.shipment.RequestedShipment.Recipient.Address.CountryCode = recipient["CountryCode"]
self.shipment.RequestedShipment.Recipient.Address.Residential = recipient["Residential"]
ti = self.shipment.create_wsdl_object_of_type("TaxpayerIdentification")
ti.Number = recipient["VAT"]
ti.TinType = "BUSINESS_NATIONAL"
self.shipment.RequestedShipment.Recipient.Tins = ti
# ----------------------------------------------------
# add "commercial invoice" reference as the only commodity
def add_ci_commodity(self):
self.addCommodity(
cCustomsValueAmnt=self.invoice_info["Value"],
cCustomsValueCurrency=self.invoice_info["Currency"],
cWeightValue=self.invoice_info["Weight"],
cDescription="See attached commercial invoice",
cQuantity=self.invoice_info["Quantity"],
cExportLicenseNumber=self.shipment.RequestedShipment.Shipper.Tins.Number,
cPartNumber=1,
)
# ----------------------------------------------------
# add commodity to shipment (for now, just add 1 commodity to refer to attached CI)
def addCommodity(
self, cCustomsValueAmnt, cCustomsValueCurrency, cWeightValue, cDescription, cQuantity, cExportLicenseNumber, cPartNumber,
):
commodity = self.shipment.create_wsdl_object_of_type("Commodity")
commodity.NumberOfPieces = str(cQuantity)
commodity.Description = cDescription
commodity.Quantity = cQuantity
commodity.QuantityUnits = "EA"
commodity.ExportLicenseNumber = cExportLicenseNumber
commodity.PartNumber = cPartNumber
commodity.CountryOfManufacture = "DK"
mCustomsValue = self.shipment.create_wsdl_object_of_type("Money")
mCustomsValue.Amount = cCustomsValueAmnt
mCustomsValue.Currency = cCustomsValueCurrency
commodity.CustomsValue = mCustomsValue
commodity_weight = self.shipment.create_wsdl_object_of_type("Weight")
commodity_weight.Value = cWeightValue
commodity_weight.Units = "KG"
commodity.Weight = commodity_weight
munitPrice = self.shipment.create_wsdl_object_of_type("Money")
munitPrice.Amount = float(round((cCustomsValueAmnt / cQuantity), 2))
munitPrice.Currency = cCustomsValueCurrency
commodity.UnitPrice = munitPrice
self.mCommodities.append(commodity)
# ----------------------------------------------------
# add package to shipment
def set_packaging_info(self):
weight = self.invoice_info["Weight"]
type = "BOX" if weight > 0.5 else "ENVELOPE"
weight_final = float(round(weight + 0.2, 2)) if weight > 0.5 else 0.4
self.addShippingPackage(packageWeight=weight_final, physicalPackagingType=type, packagingType=f"FEDEX_{type}")
# ----------------------------------------------------
# add package to shipment
def addShippingPackage(self, packageWeight, physicalPackagingType, packagingType, packageWeightUnit="KG"):
package_weight = self.shipment.create_wsdl_object_of_type("Weight")
package_weight.Value = packageWeight
package_weight.Units = packageWeightUnit
package = self.shipment.create_wsdl_object_of_type("RequestedPackageLineItem")
package.PhysicalPackaging = physicalPackagingType
package.Weight = package_weight
self.shipment.add_package(package)
self.shipment.RequestedShipment.TotalWeight = package_weight
self.shipment.RequestedShipment.PackagingType = packagingType
# ----------------------------------------------------
# add information on duties
def setDutiesPaymentInfo(self):
mParty = self.shipment.create_wsdl_object_of_type("Party")
mParty.AccountNumber = self.CONFIG_OBJ.account_number
mParty.Address = self.shipment.RequestedShipment.Recipient.Address
mPayor = self.shipment.create_wsdl_object_of_type("Payor")
mPayor.ResponsibleParty = mParty
mPayment = self.shipment.create_wsdl_object_of_type("Payment")
mPayment.PaymentType = "RECIPIENT" # change if sender should pay duties
mPayment.Payor = mPayor
mCustomsValue = self.shipment.create_wsdl_object_of_type("Money")
mCustomsValue.Amount = self.invoice_info["Value"]
mCustomsValue.Currency = self.invoice_info["Currency"]
ccd = self.shipment.create_wsdl_object_of_type("CustomsClearanceDetail")
ccd.Commodities = self.mCommodities
ccd.CustomsValue = mCustomsValue
ccd.DutiesPayment = mPayment
self.shipment.RequestedShipment.CustomsClearanceDetail = ccd
# ----------------------------------------------------
# Set ETD (electronic trade documents) settings
def setSpecialServices(self, doc_ids):
# construct objects
ssr = self.shipment.create_wsdl_object_of_type("ShipmentSpecialServicesRequested")
ssr.SpecialServiceTypes.append("ELECTRONIC_TRADE_DOCUMENTS")
ssr.SpecialServiceTypes.append("EVENT_NOTIFICATION")
# set up ETD details
etd = self.shipment.create_wsdl_object_of_type("EtdDetail")
etd.RequestedDocumentCopies = "COMMERCIAL INVOICE"
for i, doc_id in enumerate(doc_ids, start=0):
udrd = self.shipment.create_wsdl_object_of_type("UploadDocumentReferenceDetail")
udrd.DocumentType = "COMMERCIAL_INVOICE" if i == 0 else "OTHER"
udrd.DocumentId = doc_id
udrd.Description = "Commercial_Invoice" if i == 0 else "Product_Description"
udrd.DocumentIdProducer = "CUSTOMER"
ssr.EtdDetail.DocumentReferences.append(udrd)
self.shipment.RequestedShipment.SpecialServicesRequested = ssr
# set Event Notification details
send = self.shipment.create_wsdl_object_of_type("ShipmentEventNotificationDetail")
send.AggregationType = "PER_SHIPMENT"
sens = self.shipment.create_wsdl_object_of_type("ShipmentEventNotificationSpecification")
sens.NotificationDetail.NotificationType = "EMAIL"
sens.NotificationDetail.EmailDetail.EmailAddress = self.shipment.RequestedShipment.Recipient.Contact.EMailAddress
sens.NotificationDetail.EmailDetail.Name = self.shipment.RequestedShipment.Recipient.Contact.PersonName
sens.NotificationDetail.Localization.LanguageCode = "EN"
sens.Role = "SHIPPER"
sens.Events.append("ON_SHIPMENT")
sens.Events.append("ON_EXCEPTION")
sens.Events.append("ON_DELIVERY")
sens.FormatSpecification.Type = "HTML"
send.EventNotifications = sens
self.shipment.RequestedShipment.SpecialServicesRequested.EventNotificationDetail = send
# ----------------------------------------------------
# process the shipment
def processInternationalShipment(self):
from shutil import copyfile
self.shipment.RequestedShipment.ShipTimestamp = datetime.datetime.now().replace(microsecond=0).isoformat()
# print(" ---- **** DETAILS ---- ****")
# print(self.shipment.RequestedShipment)
# print(self.shipment.ClientDetail)
# print(self.shipment.TransactionDetail)
# print("REQUESTED SHIPMENT\n\n", self.shipment.RequestedShipment)
self.shipment.send_request()
# print("RESPONSE\n\n", self.shipment.response)
status = self.shipment.response.HighestSeverity
if status == "SUCCESS" and "CompletedShipmentDetail" in self.shipment.response:
shipment_details = self.shipment.response.CompletedShipmentDetail
package_details = shipment_details.CompletedPackageDetails[0]
tracking_id = package_details.TrackingIds[0].TrackingNumber
email = self.shipment.RequestedShipment.Recipient.Contact.EMailAddress
fedex_cost = "N/A"
if hasattr(package_details, "PackageRating"):
fedex_cost = package_details.PackageRating.PackageRateDetails[0].NetCharge.Amount
# create the shipping PDF label
ascii_label_data = package_details.Label.Parts[0].Image
label_binary_data = binascii.a2b_base64(ascii_label_data)
out_path = self.invoice_info["InvoiceId"] + f"_shipment_label_{tracking_id}.pdf"
out_file = open(out_path, "wb")
out_file.write(label_binary_data)
out_file.close()
# print output information
print(
f"- SUCCESS: Created FedEx label for invoice {self.invoice_info['InvoiceId']}\n tracking ID: {tracking_id}\n email: {email}\n FedEx cost: {fedex_cost}\n Customs value: {self.invoice_info['Value']} {self.invoice_info['Currency']}\n Weight: {self.invoice_info['Weight']}\n output path: {out_path}"
)
# ----------------------------------------------------
# main script
commercial_invoice_path = "commercial_invoice_test.pdf"
product_description_1_path = "product_description_test.pdf"
sender = {
"Company": "Sender Company",
"Name": "Mr Smith",
"Address": ["Address 1", "Address 2"],
"Region": "",
"Zip": "8230",
"City": "Abyhoj",
"Country": "Denmark",
"Phone": "12345678",
"Email": "mail#mail.com",
"CountryCode": "DK",
"Currency": "EUR",
"VAT": "DK12345678",
"Residential": False,
}
recipient = {
"Company": "Recipient Co",
"Name": "Contact Name",
"Address": ["Adr1, Adr2"],
"Region": "MN",
"Zip": "55420",
"City": "Bloomington",
"Country": "United States",
"Phone": "0123456789",
"Email": "mail#mail.com",
"CountryCode": "US",
"Currency": "EUR",
"VAT": "",
"Residential": False,
}
invoice_info = {
"InvoiceId": "14385",
"Weight": 0.11,
"Quantity": 2,
"Value": 20.0,
"Shipping": 25.0,
"ShippingExpress": True,
"Currency": "EUR",
"InvoicePath": commercial_invoice_path,
"Pdfs": [product_description_1_path],
}
# print output
print(f"\n- recipient: {recipient}\n- invoice_info: {invoice_info}\n")
# create FedEx Label Helper and set configuration
flh = FedexLabelHelper()
flh.setShipmentConfig(CONFIG_OBJ=CONFIG_OBJ, invoice_info=invoice_info)
# add sender & recipient info to FedEx shipment
flh.setSenderInfo(sender)
flh.setRecipientInfo(recipient)
# set packaging based on weight
flh.set_packaging_info()
# add reference to CI as only commodity info
flh.add_ci_commodity()
# set duties payment information
flh.setDutiesPaymentInfo()
# upload documents
doc_ids = flh.upload_all_documents()
# link uploaded documents as ETD and setup event notifications
flh.setSpecialServices(doc_ids)
# process shipments and create shipping labels
flh.processInternationalShipment()